<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>참신러닝 (Fresh-Learning)</title>
    <link>https://leechamin.tistory.com/</link>
    <description>이차민의 iOS/ML/DL 공부 블로그

iOS개발과 Computer Vision에 대한 전반적인 공부를 합니다.
                                          
 &amp;quot;Be The First Penguin&amp;quot;</description>
    <language>ko</language>
    <pubDate>Sat, 15 Aug 2026 07:07:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발차</managingEditor>
    <image>
      <title>참신러닝 (Fresh-Learning)</title>
      <url>https://tistory1.daumcdn.net/tistory/3013744/attach/c30b7f6cbbc54bd9b74f8bcec0d2300d</url>
      <link>https://leechamin.tistory.com</link>
    </image>
    <item>
      <title>[하이퍼커넥트] 2022 iOS Software Engineer 인턴 후기</title>
      <link>https://leechamin.tistory.com/560</link>
      <description>&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;1025&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o8fLz/btrFHczhv89/ZPdMQcpKyu87CoLAHpEjwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o8fLz/btrFHczhv89/ZPdMQcpKyu87CoLAHpEjwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o8fLz/btrFHczhv89/ZPdMQcpKyu87CoLAHpEjwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo8fLz%2FbtrFHczhv89%2FZPdMQcpKyu87CoLAHpEjwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1025&quot; height=&quot;301&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱 3달만에 글을 작성하네요...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 3월에 지원했던 하이퍼커넥트 Azar Studio, iOS Software Engineer 인턴 과정을 끝마쳤기 때문입니다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인턴 과정을 마치고 나서 내부적으로도 피드백을 주고 받고, 회고하는 시간을 가졌지만 이와 별개로 작성해보려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 달 단위로 나눠서 그 당시 들었던 감정, 중요하다고 느꼈던 부분들에 대해 짚어보면서 정리해보겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(진행했던 프로젝트, 과정에 대한 상세 내용은 언급하지 않을 예정입니다  그렇다보니 추상적일 수도...)&lt;/i&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정신이 없었던 4월&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3월 마지막 주부터 인턴으로 합류하여 정식으로 과제를 부여받고 진행하기 시작했습니다. 과제를 어떤식으로 진행할지 함께하는 인턴분과 이야기를 많이 나누었고, 사실상 4월에 들어서고 나서부터 본격적으로 프로젝트를 진행했습니다. 간단한 코드/git 컨벤션부터 프로젝트의 아키텍쳐에 대해 이야기 나누며 틀을 잡고, 문서화 방법, 일정 산정 등에 대해 세세하게 이야기를 나누고 작업을 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 야곰 아카데미에서 git도 써보고, 협업도 해보고, 프로젝트의 요구사항에 대한 일정 산정, 기능 구현을 모두 경험해봤기에 어느 정도 도움이 되었던 것 같습니다. 그럼에도 병렬적으로 업무를 진행하는 부분은 경험해보지 못했던 부분이었고, 이에 동반하여 발생하는 git 충돌에 대해서도 많이 경험해보지 못했던 것 같습니다. 그래서인지... 이번에 엄청 많은 git 충돌을 겪었고, 초기에는 이 문제를 해결하는데 시간을 생각보다 많이 소모했던 기억이 있습니다. 그래도 처음에 잘 학습해둔 덕분인지 프로젝트 마지막에 다다랐을 때는 git을 사용한 협업에는 어느정도 자신감이 붙었던 것 같습니다. (rebase 지옥...충돌은 당연한 것...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 재택 근무 형태로 인턴 생활을 함에 있어, 걱정할 법도 했지만... &quot;캠프도 6개월간 비대면으로 했는데 뭐&quot;라는 생각에 걱정을 덜 수 있었습니다. 그래서인지 거의 매일 하루종일 google meet을 켜두고 협업을 했던 것 같습니다. 서로 커뮤니케이션이 필요한 경우에 슬랙의 DM을 보낼 수도 있지만, 수시로 의견 교환이 이루어져야 하는 경우가 주로 초반에 많았습니다. 물론 불편한 점은 아니었지만, 하루 업무를 마치고 나면 진이 빠지는 느낌을 받긴 했습니다...&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;또 iOS 직무로는 인턴이 처음이다보니, 과제를 수행하기 위한 일정을 정해두고 작업을 진행했음에도 생각보다 일정 맞추는게 어려웠습니다. 이에 중간쯤에 일정에 무리가 있을 것 같다고 판단하여 일정을 조금 연장하는 등 조치를 취하긴 했습니다. 이 과정에서 당연히 물리적인 시간이 많이 필요했기 때문에 필요할 때는 추가 근무를 하며 완수할 수 있도록 했습니다. (물론 야근비는 챙겨줍니다  )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 이외에도 특정 주제에 대한 발표를 해야하는 것 또한 인턴으로서 하나의 업무였는데, 이 또한 생각보다 쉽지는 않았습니다... 캠프에 있으면서 발표는 많이 경험해보지 못했고, 사실 학부생으로 있을 때 있었던 발표 경험을 배경 지식 삼아... 전공도 다르지만... 우선 그렇게 진행했습니다. 발표를 준비하면서는 최대한 &quot;왜&quot;라는 관점에서 바라보려고 노력했으며, 덕분에 근본이 되는 개념들에 대해 궁금증을 가지고 이해해 볼 수 있었습니다.&lt;/p&gt;
&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;  적응하는 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  합을 맞추는 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  초반 스퍼트&lt;/b&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;적응하고 성장했던 5월&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시기가 프로젝트를 진행함에 있어도 가장 성장했다고 느꼈던 구간인 것 같습니다. 주로 테스트를 작성하는 그 자체 뿐만 아니라, 테스트 가능한 구조를 만드는 것에 집중하여 다양한 개념들을 학습했습니다. 기존에 학습했었던 의존성 주입을 기반으로 하여 객체들 간의 강결합을 방지하고, 테스트 케이스를 작성할 때 다양한 테스트 더블을 사용하며 테스트 코드를 작성했습니다. 이 과정에서 프로젝트를 구조적인 관점에서 볼 수 있게 되었다고 생각했습니다. 요구되는 기능들 자체에 각각 집중하기 보다는 전체적인 흐름에 집중하면서 구조를 바라볼 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속되는 요구사항들을 충족하며, 이러한 구조에 신경을 많이 썼음에도 불구하고 현재 시점에서 바라봤을 때도 아직은 부족한 느낌이 들고 있어서... 어떻게 해야 개선해볼 수 있을지에 대한 고민은 여전히 진행중입니다... &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 프로젝트를 진행하면서 처음 접해본 개념, 잘 모르겠는 부분들을 해결하기 위해 무한 검색을 하다가 답을 못찾겠을 때, 팀 내에 질문을 한다거나 멘토님께 질문을 해서 해결을 위한 힌트를 얻을 수 있었습니다. 이 과정에서 깨달았던 것은 스스로 질문을 하기까지의 과정이 오래 걸린다는 것이었습니다. 문제를 마주했을 때, 바로 질문하는 것도 좋지 않지만, 너무 늦게 질문하는 것도 좋지 않다는 것을 인지하고는 있었으나, 스스로 해결해보고 싶은 마음에 질문하기에 적당한 시기를 놓쳤던 경험이 있습니다. 이에 스스로 하루면 하루, 이틀이면 이틀을 정해두고 기한을 넘기는 경우에는 주변의 도움을 받는 것이 효율적이고 바람직하다는 것을 깨닫게 되었습니다.&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;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;&lt;b&gt;⚒️ 테스트 + 테스트 가능한 구조&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  일정 산정의 중요성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;zwj;♂️ 퀄리티있는 질문에 대한 고민&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 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실수했었던 6월&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 잘나가다가 실수했던 6월인가 하면... 앞서 말했듯이 일정 산정에 대한 중요성을 인지는 하고 있었으나 제대로 지키지 못했기 때문입니다  같은 실수를 반복하지 않기 위해 지키지 못했던 이유에 대해서는 함께했던 인턴분과도 많은 회고를 했습니다.&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;먼저 학습하는 입장에서는 이해가 되지 않는 부분들에 대해서 PR을 보내는 것이 조금 꺼려졌었습니다. 캠프에서 학습했다 싶이 이유있는 코드를 작성해야할 필요성이 있고, 스스로 코드를 이해하고 있어야 이를 기반으로 소통할 수 있다고 생각했습니다. 다만 요구사항을 구현하는 와중에 낯선 도메인 + 프레임워크 지식이 필요하다는 것을 뒤늦게 알게 되었고, 이를 활용하여 구현을 진행하다보니 제대로 이해하지 못한 코드들이 프로젝트에 적용되어 있는 것을 볼 수 있었습니다. 이에 이해를 우선적으로 앞세우다 보니, 일정을 제대로 고려하지 못했고 이러한 문제 상황이 발생했던 것 같습니다. 결론적으로 회고를 했을 때, 추후 일정을 산정함에 있어 요구사항을 파악할 때 조금 더 세분화해서 나누어보고, 이를 구현하기 위해 필요한 것들을 나열해보고 사전 조사를 해보는 시간을 가져야겠다고 생각했습니다. 그러한 과정을 거쳐야 내가 무엇을 모르고, 어떤 것을 집중적으로 학습하고 적용해봐야하는 지에 대해서 정리해볼 수 있기 때문에 꼭 필요하다고 느꼈습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 일정 부분을 제외하고 모든 부분은 완벽했다! 이건 또 당연히 아닙니다... 초반에 선정한 아키텍처의 흐름을 벗어나는 방식으로 코드가 작성되거나, 테스트 가능하지 못한 구조로 구현을 하는 등 꾸준히 어렵다고 느꼈던 것들에 대해서 문제가 생겼던 것 같습니다. 끝난시점에서 이 부분을 개선점으로 생각하고 있고, 아쉬운 부분이라고도 생각합니다. 어떻게 했어야 과정 중에서 온전히 해결할 수 있었을까 생각하다가도, 뭔가 간단하게 해결할 수 있는 문제가 아닌 것 같다고도 생각했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 할 수 있는 부분까지 최선을 다해서 마지막 요구사항까지 충족을 하고, 프로젝트에 대한 발표도 진행하고, 인턴 과정 중에 있었던 좋았던/아쉬웠던 점들에 대해 이야기하면서 과정을 잘 마칠 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6월도 정리해보자면..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  여전히 중요한 일정 산정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  도메인 + 프레임워크 학습&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰다보니 3개월간 밀도있게 학습했던 것에 비해서 엄청나게 함축시켜서 작성한 것 같긴한데...&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;본 인턴 과정에서 프로젝트를 진행하면서 코드 리뷰를 해주시고, 질문에 좋은 답변을 해주신 Azar Studio iOS 팀원 분들께 압도적인 감사를 표하고 싶습니다 &amp;zwj;♂️ 뿐만 아니라 팀 내에서 진행하는 기술 공유 세션이나, 코드 리뷰, 오고가는 질문들을 통해 팀에 합류한다면 많이 배울 수 있겠다는 느낌을 받았었는데, 그 덕분에 인턴 과정을 좀 더 열심히 할 수 있도록 해준 동기부여가 되지 않았나 생각합니다.&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;아무튼... 다시 돌아와서 그렇게 인턴 3개월을 마치고 나서 전환면접의 기회를 얻을 수 있을지 긴장하고 있었는데... &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;348&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NxTD5/btrFGe5F0T5/tGf8xGZfYuLhVehW7M2N2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NxTD5/btrFGe5F0T5/tGf8xGZfYuLhVehW7M2N2k/img.png&quot; data-alt=&quot;전환면접 초대!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NxTD5/btrFGe5F0T5/tGf8xGZfYuLhVehW7M2N2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNxTD5%2FbtrFGe5F0T5%2FtGf8xGZfYuLhVehW7M2N2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;27&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전환면접 초대!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 좋은 평가를 받았는지 정규직 전환 면접의 기회를 얻을 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접에서는 3개월 간의 여정이 어땠는지, 어느 정도의 성장을 했는지, 어려웠던 점은 무었이었는지 등에 대해 이야기를 나누었으며, 한 번 더 3개월의 시간에 대해 되돌아 볼 수 있었다.&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;마지막 전환 면접에서 나름 좋은 시그널이라고 볼 수 있는 표현들을 발견했지만... 뭔가 확정적으로 &quot;너 전환이야!&quot;라는 말을 들어야만 마음이 안정될 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 면접 바로 다음 날 알 수 있었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9Mql/btrFGTz0IQV/pAXkct1o4hhKlRsBAkcFGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9Mql/btrFGTz0IQV/pAXkct1o4hhKlRsBAkcFGK/img.png&quot; data-alt=&quot;담당자님의 DM!  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9Mql/btrFGTz0IQV/pAXkct1o4hhKlRsBAkcFGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9Mql%2FbtrFGTz0IQV%2FpAXkct1o4hhKlRsBAkcFGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;48&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;담당자님의 DM!  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메시지를 보고 마음 한 켠에 남아있던 걱정이 사라졌다... 참 다행이라고 생각했고, 동시에 앞으로 어떻게 하면 더 발전하고 성장할 수 있을지 고민이 되기 시작했다... 끝없는 고민과 학습의 연속... &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 기회를 얻게 된만큼, 이를 적극 활용하여 폭풍 성장하고 싶은 마음이 크다. 지난 퇴사 이후 독학하던 시절, 캠프에서의 6개월, 인턴 3개월 모두 열정적으로 학습했던 것 처럼 앞으로 마주하고 있는 날들에 대해서도 후회와 아쉬움이 남지 않도록 열심히 배워서 성장해야겠다는 생각이 들었다.&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인분 할 수 있도록 노력해야 할 것 같고, 빠르고 가파르게 성장하기 위한 길들을 모색해봐야겠다.&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;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;</description>
      <category> &amp;zwj;♂️취업준비</category>
      <category>Azar Studio</category>
      <category>ios</category>
      <category>하이퍼커넥트</category>
      <category>하이퍼커넥트 iOS 인턴</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/560</guid>
      <comments>https://leechamin.tistory.com/560#entry560comment</comments>
      <pubDate>Fri, 24 Jun 2022 21:12:36 +0900</pubDate>
    </item>
    <item>
      <title>[하이퍼커넥트] 2022 iOS Software Engineer 인턴 지원기</title>
      <link>https://leechamin.tistory.com/559</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 하이퍼커넥트 인턴 전형에 지원하고 결과를 받기까지의 과정을 회고해보려고 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UVn2b/btrwJv11oOr/7JR5NM8Nvnfl5QnzqR9b51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UVn2b/btrwJv11oOr/7JR5NM8Nvnfl5QnzqR9b51/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UVn2b/btrwJv11oOr/7JR5NM8Nvnfl5QnzqR9b51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUVn2b%2FbtrwJv11oOr%2F7JR5NM8Nvnfl5QnzqR9b51%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;268&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우연한 기회로 &lt;a href=&quot;https://career.hyperconnect.com/job/173cfeb4-f63a-4a96-9a84-60045242bea4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS Software Engineer 인턴 채용 공고&lt;/a&gt;를 발견했었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이퍼커넥트의 메인 서비스라고도 할 수 있는 Azar 클라이언트 개발팀에서 모집하는 공고로 비디오와 AI 기반의 글로벌 서비스라는 점이 매력적으로 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일단 담당업무와 지원자격을 확인하고 바로 지원서를 작성했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이전에 작성해뒀던 이력서, 포트폴리오가 있어서 조금 더 보완하는 식으로 진행했다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5kRT/btrwA0IAjOI/ccJGB0itmEsVVms3SKBLR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5kRT/btrwA0IAjOI/ccJGB0itmEsVVms3SKBLR0/img.png&quot; data-alt=&quot;담당업무와 지원자격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5kRT/btrwA0IAjOI/ccJGB0itmEsVVms3SKBLR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5kRT%2FbtrwA0IAjOI%2FccJGB0itmEsVVms3SKBLR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;243&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;282&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;다른 인턴 공고들과 달리 &lt;b&gt;&quot;학습&quot;&lt;/b&gt;에 초점이 맞춰 강조하고 있는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 MVVM 패턴을 접하기도 했도, 끝없는 클로저 지옥에 빠져본 경험이 있어 RxSwift도 궁금해하던 찰나에 딱 맞는 좋은 기회라고 생각했다. 그리고 업무가 학습이라니...! 회사에서도 돈 주고 공부만 시키는 건 타산이 맞지 않으니, 당연히 투자일거라고 생각했다. 이 사람이 더 성장할 수 있는 사람인지, 학습 태도라거나, 학습 습관, 속도 등, 전반적으로 같이 일할 수 있는 사람인지 판단하는 기간이 아닐까 싶었다.&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;서류를 작성했을 당시 야곰 아카데미에서 학습한지도 어언 5개월을 넘어가는 시점이었기도 했고, 수료하기 전에 실무에서 일할 수 있는 기회를 얻어보고 싶다는 생각을 자주 많이 했었다. 항상 개발 공부를 하면서 이런 상황에 실무에서는 어떻게 처리할까? 어떤 의사결정 과정을 거칠까 많이 궁금했다. 이를 해소하기 위해 캠프 리뷰어에게 묻는다거나, 구글링하면서 기술블로그를 들춰보는 등 간접적인 경험 뿐인게 조금 아쉬웠던 찰나에 인턴은 아주 좋은 기회라고 생각했다.&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;&quot;서류 전형 -&amp;gt; 코딩 테스트 -&amp;gt; 1차 면접 -&amp;gt; 2차 면접 -&amp;gt; 최종 합격&quot;&lt;/b&gt; 순으로 진행되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(공고에는 &quot;화상 면접&quot;이라고만 적혀있었으나 두 차례 면접이 진행되었다...!)&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;1. 서류 전형&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원 페이지를 가보니 간단하게 &lt;b&gt;&quot;이력서 + 포트폴리오&quot;&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;대략 추려서 4개 정도 기입한 것 같은데, 사실 지금봐도 크게 만족스럽진 않다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사이드 프로젝트를 하나 정해서 전반적인 설계를 중심으로 구현해보는 경험을 해보면 좋겠다고 생각하고 있다. 리팩토링은 덤 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 학습한 내용에 있어서 거짓없이 솔직하게 써내려갔고, 한 번씩 다시 살펴보고 제출을 했다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말쯤 제출했던 것 같은데, 상당히 빠르게 결과를 받아볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/utns2/btrwIivRkZg/h6gtNpsXSbTZRpKc3a66Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/utns2/btrwIivRkZg/h6gtNpsXSbTZRpKc3a66Zk/img.png&quot; data-alt=&quot;서류 전형 통과!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/utns2/btrwIivRkZg/h6gtNpsXSbTZRpKc3a66Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Futns2%2FbtrwIivRkZg%2Fh6gtNpsXSbTZRpKc3a66Zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;45&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;42&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;다행히도 좋게봐주셨는지 서류 전형에 합격할 수 있었다!&lt;/p&gt;
&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;2. 코딩 테스트&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 테스트는 iOS 공부를 시작하고 기회가 있을 때 마다 코딩 테스트가 있는 전형에 지원해서 응시했던 것 같다. 실력이 되지 않더라도 기업 코테에서는 어떤 문제들이 나오는지, 호기심 때문에 응시하게 되었던 것 같다. 당연히 제대로 학습이 된 상태가 아니었기 때문에 손도 못대는 문제들도 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업 코딩 테스트를 한 번 두 번 경험할 때마다, &quot;아... 진짜 코테만 공부해도 될까말까하겠구나...&quot; 싶었다.&lt;/p&gt;
&lt;p data-ke-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;아무튼 leetcode, 프로그래머스, 백준 등의 플랫폼에서 문제들을 많이 푼다고는 했지만 실력이 많이 향상된 것 같은 기분은 받지 못했다. 그래도 여러 문제를 접해보니, 문제를 봤을 때 어떤 유형으로 접근해야하는지 정도의 감은 조금 생겼던 것 같다. 한창 알고리즘 공부가 재밌을 때(?)도 있어서 그 때 많이 풀이했던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼! 유형별로 준비를 해서 코딩 테스트 일정에 맞게 응시했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 기간 내에 자유롭게 선택해서 응시하면 되는 구조였다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 또한 지원자들에 대한 배려가 아닐까 싶었다 )&lt;/p&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문제로 계산해보니 거의 한 문제당 30분 내에 풀어야 했기 때문에 조급한 마음으로 하나하나 풀어가기 시작했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한 실수하지 않으려고 했고, 문제를 풀기 전에 주석으로 풀이를 위한 logic을 적어 정리를 하고 코드를 작성했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코테 볼 때는 항상 시계가 어떻게 된건지... 항상 너무 빨리 흐른다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 엄청나게 어려워서 손도 못댈 정도는 아니었던 것 같아서, 진짜 다 맞아야겠다는 생각으로 풀이해서 제출했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 항상 내가 풀 때는 맞지만... 생각하지 못한 히든 케이스 때문에 틀리는 경우가 많기 때문에 풀고 나서도 크게 확신이 있진 않았다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 시험은 끝났으니 미련은 갖지 않기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 주말은 지나갔고...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYjWq0/btrwJCtBnH7/OPJ29xUhzKC61p5YpryKrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYjWq0/btrwJCtBnH7/OPJ29xUhzKC61p5YpryKrK/img.png&quot; data-alt=&quot;코딩 테스트 통과!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYjWq0/btrwJCtBnH7/OPJ29xUhzKC61p5YpryKrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYjWq0%2FbtrwJCtBnH7%2FOPJ29xUhzKC61p5YpryKrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;524&quot; height=&quot;40&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;40&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;코테 합격이라니...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 1차 면접... 발등에 불 떨어졌다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 1차 면접&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;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;li&gt;Swift&lt;/li&gt;
&lt;li&gt;CS&lt;/li&gt;
&lt;li&gt;OOP&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 네 가지 위주로 쭉 한 번 점검해보기로 하고 계획을 세웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 준비를 해봤던 분들이라면 다들 알만한 github repo들을 참고해서 나름대로 찾아보고 생각을 섞어서 정리해보는 시간을 가졌다.&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;a href=&quot;https://github.com/JeaSungLEE/iOSInterviewquestions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/JeaSungLEE/iOSInterviewquestions&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;iOS와 Swift는 위 repo를 기반으로 점검했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/JaeYeopHan/Interview_Question_for_Beginner/blob/master/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/JaeYeopHan/Interview_Question_for_Beginner/blob/master/README.md&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;CS는 위 repo도 참고했고, 이전에 KOCW 강의를 보며 정리했던 내용들을 다시 보면서 복기했다. 세부적으로는 운영체제, 자료구조, 알고리즘, 네트워크, 데이터베이스에 대해 살펴봤던 것 같다. 그 중에서도 운영체제, 네트워크에 조금 집중해서 공부했던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 경험이 없다보니... 지난 당근마켓 면접에서 대답하지 못했던 개념들을 참고해서 집중적으로 학습했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 하나의 개념에 대해서 깊이 설명할 수 있도록 준비했다. 꼬리에 꼬리를 무는 질문에 대답할 수 있을 정도로 준비하는 것이 목표였다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 면접까지 시간이 많이 남아있지 않았기 때문에 조금 서둘러서 준비했던 것 같다.&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시간 정도 기술 질문 위주의 면접이 진행되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 지식뿐만 아니라 꼬리에 꼬리를 무는 질문도 많았고, 자리에서 생각해봐야 하는 질문들도 쏟아졌다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 자리도 오랜만이었고, 대답을 제대로 하지 못하니 조금 위축되는 느낌이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 면접에 들어가기 전부터 다짐했던 것 처럼, 모르면 솔직하게 모른다고 이야기하고 학습해보겠다고 말했다.&amp;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;면접은 개인적으로 어려웠던 것 같다... 세상에 쉬운 면접이 어디있겠느냐만은...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우여곡절 끝에 1차 면접을 마치고 화상 회의실을 나오고 나서야 한숨 돌릴 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제대로 대답하지 못했다는 생각에 조금 불안했지만... 우선 대답하지 못했던 질문들에 대한 답도 궁금했어서 질문들을 복기하고 해당하는 답들을 찾아보면서 생각을 정리했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 결과 발표를 손모아 기다렸다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZr7Ye/btrwA6XjJsS/TbDiktkQYacJePbo7DvQqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZr7Ye/btrwA6XjJsS/TbDiktkQYacJePbo7DvQqK/img.png&quot; data-alt=&quot;1차 면접 통과!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZr7Ye/btrwA6XjJsS/TbDiktkQYacJePbo7DvQqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZr7Ye%2FbtrwA6XjJsS%2FTbDiktkQYacJePbo7DvQqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;44&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1차 면접 통과!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋게 봐주셨는지 1차 면접에 합격할 수 있었다ㅠㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합격했다는 기쁨도 잠시 넣어두고 2차 면접을 준비하기 시작했다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 2차 면접&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서 여러 2차 면접 후기들을 찾아보니, 1차 면접에서 했던 질문들을 다시 물어보는 경우가 많았다는 글들을 볼 수 있었다. 내가 면접관이라도 1차 때 대답하지 못했던 질문들을 물어보지 않을까? 생각이 들었다. 아무튼 그 생각을 갖고 1차 면접 질문들을 복기하고 CS지식을 다시 살펴봤다. (1차 면접을 준비하면서 정리했던 자료들을 많이 보면서 직접 말로 설명해보는 연습을 했던 것 같다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차 때도 그랬지만, 입을 좀 더 트이게 하려고 혼자 면접관, 면접자 역할을 하면서 시뮬레이션을 했다..ㅋㅋㅋ&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;그렇게 2차 면접을 진행했고, 마찬가지로 1시간 정도 소요된 것 같다. 1차와 마찬가지로 다양한 질문들이 쏟아졌고 학습했던 내용, 프로젝트 경험, 개발에 대한 생각 등을 차근차근 정리해서 말씀드렸다. 편하게 이야기 할 수 있는 분위기를 만들어주셔서 1차보다는 수월하게 답변했다는 생각이 들었다.&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;사실 2차 면접날이 이사 전 날이어서 많이 정신이 없었다... 집에는 짐이 쌓여있고, 당장 면접 끝나면 또 짐을 싸야하고... 혼란 속에서 2차 면접을 본 것 같았다. 그래도 최대한 집중해서 답변했던 것 같다!&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;2차는 처음이라... 내가 잘 본건지 아닌지도 느낌이 잘 오지 않았다. 흔히 말하는 면까몰(?)이라고 해서 &quot;면접은 까보기 전까지 모른다&quot;는 말이 생각났고, 이미 내 손을 떠난 일이라고 생각하니 마음은 조금 편해졌다...그래도 후회없이 준비하고, 답변해낸 것 같아서 개인적으로는 굉장히 좋은 경험이었다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 주말에 이사도 마치고... 차주 중에 결과를 받아볼 수 있다고 하셔서 경건히 기다리기로 마음 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 생각보다 일찍 결과를 받아볼 수 있었다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;23&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uyzmu/btrwA6XkprU/UwwMLYrj9QyQqFBJidUx01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uyzmu/btrwA6XkprU/UwwMLYrj9QyQqFBJidUx01/img.png&quot; data-alt=&quot;최종 합격!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uyzmu/btrwA6XkprU/UwwMLYrj9QyQqFBJidUx01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuyzmu%2FbtrwA6XkprU%2FUwwMLYrj9QyQqFBJidUx01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;23&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;23&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; &lt;/p&gt;
&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;&quot;최종&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전반적인 경험&lt;/b&gt;&lt;/h2&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;span&gt;굉장히 빠르게&lt;span&gt; &lt;/span&gt;&lt;/span&gt;진행되었던 것 같다. 2월 10일 쯤 공고를 확인하고 마감일 전인 15일에 지원했으니 최종합격까지 1달 조금 넘게 걸린 것 같다. 매 전형마다 거의 짧게는 2일, 아무리 길어도 5일 이내에 모든 결과를 받아볼 수 있었다. 빠르게 결과를 안내해주시는 모습이 인상깊었다. 중간중간 안내해주시는 인사담당자님도 일정을 잡거나, 안내해주실 때 면접자를 많이 배려해주시는 듯한 느낌을 많이 받았다.&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;span&gt;면접&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;면접 경험이 많지는 않지만...!&lt;span&gt;&amp;nbsp;면접자가&lt;/span&gt;&lt;/span&gt; 편하게 대답을 할 수 있는 분위기가 1,2차 공통적으로 있었던 게 좋았다. 대답하지 못한 질문에 대해서도 조금의 힌트를 주시면서 추론해볼 수 있게 유도해주셨고, 나 또한 그 덕분에 질문에 대한 답변을 추론해볼 수 있었던 것 같다. 또 단순히 외워서 대답할 수 있는 질문 뿐만 아니라 생각하게 되는 질문들도 많아서 유익했고 개인적으로 면접을 통해서 조금이나마 성장한 것 같다!&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채용 과정을 거치면서 개인적으로 하이퍼커넥트에 대한 인식이 더 좋아진 것 같다! 그 만큼 긍정적인 인상을 많이 받았다는 것으로 이해해도 좋을 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개월 후 인턴 후기로 돌아오겠습니다!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;♂️취업준비</category>
      <category>하이퍼커넥트</category>
      <category>하이퍼커넥트 iOS 인턴</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/559</guid>
      <comments>https://leechamin.tistory.com/559#entry559comment</comments>
      <pubDate>Wed, 23 Mar 2022 17:50:33 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] UIImage resizing (feat. UIGraphics)</title>
      <link>https://leechamin.tistory.com/558</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;code&gt;UIImage&lt;/code&gt; resizing&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UIImage&lt;/code&gt;가 너무 큰 경우 보통은 auto layout을 통해 너비,높이를 잡아주거나 top,leading,trailing,bottom을 주어서 정해진 마진을 지키면서 이미지가 들어가도록 구현한 경험이 있을 것이다.&lt;/p&gt;
&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;code&gt;UIGraphicsBeginImageContextWithOptions(_:_:_:)&lt;/code&gt;를 사용하는 방식을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 순서를 먼저 알아보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;UIGraphicsBeginImageContextWithOptions(_:_:_:)&lt;/code&gt;로 비트맵 만들기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UIView.draw()&lt;/code&gt;로 전달받은 사이즈만큼 다시 그려주기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UIGraphicsGetImageFromCurrentImageContext()&lt;/code&gt; 메서드를 통해 크기가 조정된 이미지를 받아오기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UIGraphicsEndImageContext()&lt;/code&gt;를 호출하여 비트맵을 그려주는 환경을 치워주기!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. &lt;code&gt;UIGraphicsBeginImageContextWithOptions(_:_:_:)&lt;/code&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func UIGraphicsBeginImageContextWithOptions(_ size: CGSize, 
                                          _ opaque: Bool, 
                                          _ scale: CGFloat)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 옵션의 비트맵 기반의 그래픽 배경을 만들어주는 역할을 한다. 총 세 가지의 파라미터가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 비트맵 배경(context)의 크기를 나타낸다.&lt;/li&gt;
&lt;li&gt;이는 &lt;code&gt;UIGraphicsGetImageFromCurrentImageContext()&lt;/code&gt; 메서드를 호출하여 반환할 이미지의 크기를 나타내기도 한다.&lt;/li&gt;
&lt;li&gt;비트맵의 크기를 픽셀단위로 가져오려면, 이미지의 너비와 높이에 &lt;code&gt;scale&lt;/code&gt; 파라미터로 오게 될 값을 곱해주면 된다.&lt;/li&gt;
&lt;li&gt;즉 &lt;code&gt;너비 * 높이 * scale&lt;/code&gt;을 의미하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;opaque&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비트맵이 불투명한지 여부를 나타내는 Bool 타입의 값이다.&lt;/li&gt;
&lt;li&gt;비트맵이 완전히 불투명하다는 것을 알고 있다면 값을&lt;code&gt;true&lt;/code&gt;로 하여 alpha 채널을 무시하고 비트맵의 저장소를 최적화 할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;false&lt;/code&gt;로 할 경우, 비트맵에 부분적으로 투명한 픽셀을 처리할 수 있는 alpha 채널이 포함되어야 한다는 것을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scale&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비트맵에 적용할 축적 비율(scale factor)이다.&lt;/li&gt;
&lt;li&gt;값을 0.0으로 주면 scale factor가 기기의 main screen의 scale factor로 설정된다.&lt;/li&gt;
&lt;li&gt;기기마다 스케일 값은 다른데, 스케일 값은 &lt;code&gt;UIScreen.main.scale&lt;/code&gt;로도 확인해 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드를 통해 생성된 배경(context)가 현재 배경이며, &lt;code&gt;UIGraphicsGetImageFromCurrentImageContext()&lt;/code&gt; 메서드를 호출하여 배경의 현재 내용을 기반으로 이미지 객체를 얻을 수 있다. 그리고 배경의 수정을 마치면 무조건 &lt;code&gt;UIGraphicsEndImageContext()&lt;/code&gt; 메서드를 호출해서 비트맵을 그리는 환경을 정리하고, 배경 스택(context stack)의 맨 위에서 그래픽 배경(graphic context)를 제거해야한다. 스택에서 이러한 타입의 배경을 제거하는데 &lt;code&gt;UIGraphicsPopContext()&lt;/code&gt;메서드를 사용하면 안된다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 다른 그래픽 배경을 가져오고 싶다면 push/pop을 하여 배경을 변경할 수 있다. 또한 &lt;code&gt;UIGraphicsGetCurrentContext()&lt;/code&gt; 메서드를 사용하여 비트맵 배경을 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이 메서드의 경우 앱의 모든 쓰레드에서 호출할 수 있다고 하니 참고하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. &lt;code&gt;draw(in: )&lt;/code&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func draw(in rect: CGRect)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 이미지를 특정한 사각형 내에 그려주어 필요에 따라 크기를 조정해주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터는 1개로 간단하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;rect&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지를 그려줄 사각형(그래픽 context의 좌표계에서의 사각형!)을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 설정된 이미지의 방향을 고려하여 현재 그래픽 배경에 전체 이미지를 그려준다. 기본 좌표계에서 이미지는 지정된 사각형의 원점 오른쪽 아래쪽에 위치한다. 하지만 이 방법은 현재 그래픽 배경에 적용된 모든 변환들을 고려해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 &lt;code&gt;CGBlendMode.normal blend&lt;/code&gt; 모드를 사용하여 이미지를 불투명하게 그려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이미지를 그렸으니 얻어오기만 하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. &lt;code&gt;UIGraphicsGetImageFromCurrentImageContext&lt;/code&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func UIGraphicsGetImageFromCurrentImageContext() -&amp;gt; UIImage?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널 타입의 &lt;code&gt;UIImage&lt;/code&gt;를 반환해주는 메서드이다. 이제 크기가 조정된 이미지를 받아줄 수 있는 것이다. 조금 더 자세히 이야기해보면, 현재 비트맵 그래픽 배경의 내용을 포함하는 이미지 객체를 반환해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 비트맵 기반 배경이 현재 그래픽 배경인 경우에만 이 메서드를 호출해야 한다. 즉, 앞에서 그래픽 배경(context)를 설정해주고 나서 호출해줘야 한다는 것이다. 그렇지 않고 현재 배경이 nil이거나 아직 &lt;code&gt;UIGraphicsBeginImageContext(_:)&lt;/code&gt; 메서드 호출을 통해 생성되지 않은 경우에 위 메서드는 nil을 반환하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드 또한 앱의 모든 쓰레드에서 호출될 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UIGraphicsBeginImageContext(_:)&lt;/code&gt;는 또 뭔가..싶긴한데 아까 1번 과정에서 본 메서드랑 굉장히 유사하게 생긴 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로도 문서를 보면 &lt;code&gt;UIGraphicsBeginImageContextWithOptions(_:_:_:)&lt;/code&gt;의 opaque가 false이고, scale은 1.0으로 세팅된 메서드를 호출하는 것과 같다고 한다. 그래서인지 &lt;code&gt;CGRect&lt;/code&gt;만 받도록 아래와 같이 구현되어 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func UIGraphicsBeginImageContext(_ size: CGSize)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. &lt;code&gt;UIGraphicsEndImageContext()&lt;/code&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func UIGraphicsEndImageContext()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택의 맨 위에서 현재 비트맵 기반 그래픽 배경을 제거하는 역할을 한다. 즉, 다 그리고 이미지를 사용하고나면 제거해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드를 사용하여 &lt;code&gt;UIGraphicsBeginImageContext(_:)&lt;/code&gt; 메서드로 생성된 환경을 정리하고, 스택 상단에서 해당 비트맵 기반 그래픽 배경을 제거할 수 있다. 현재 배경이 &lt;code&gt;UIGraphicsBeginImageContext(_:)&lt;/code&gt; 메서드를 통해 생성되지 않은 경우 이 메서드는 아무것도 수행하지 않는다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드 또한 앱의 모든 쓰레드에서 호출될 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 구현 코드&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;code&gt;UIView&lt;/code&gt;를 extension하여 위 메서드들을 사용해서 이미지를 리사이징하는 방법을 구현해보자!&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;extension UIImage {
    func resizeImageTo(size: CGSize) -&amp;gt; UIImage? {
        // 비트맵 생성
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        // 비트맵 그래픽 배경에 이미지 다시 그리기
        self.draw(in: CGRect(origin: CGPoint.zero, size: size))
        // 현재 비트맵 그래픽 배경에서 이미지 가져오기
        guard let resizedImage = UIGraphicsGetImageFromCurrentImageContext() else {
            return nil
        }
        // 비트맵 환경 제거 
        UIGraphicsEndImageContext()
        // 크기가 조정된 이미지 반환
        return resizedImage
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 앞선 설명을 그대로 코드로 갖다 넣은 정도이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이제 입맛에 맞게 이미지 크기를 조정해서 사용해 볼 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 크기 자체가 줄어든다는 장점도 있겠지만, 이미지 자체가 줄어들기에 이미지 용량 또한 줄어든다는 것도 장점으로 꼽을 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끄읕&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/1623922-uigraphicsbeginimagecontext&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/1623922-uigraphicsbeginimagecontext&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/1623912-uigraphicsbeginimagecontextwitho&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/1623912-uigraphicsbeginimagecontextwitho&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/1623924-uigraphicsgetimagefromcurrentima&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/1623924-uigraphicsgetimagefromcurrentima&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/1623933-uigraphicsendimagecontext&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/1623933-uigraphicsendimagecontext&lt;/a&gt;&lt;/p&gt;</description>
      <category> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <category>swift image resizing</category>
      <category>UIImage Resizing</category>
      <category>이미지 리사이징</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/558</guid>
      <comments>https://leechamin.tistory.com/558#entry558comment</comments>
      <pubDate>Thu, 20 Jan 2022 02:21:13 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] WWDC16: Understanding Swift Performance 1</title>
      <link>https://leechamin.tistory.com/557</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WWDC16: Understanding Swift Performance 1&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/qL8bcU7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 여러 타입들 가운데 어떤 타입을 써야할지는 여전히 매번 고민이되는 주제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조가 필요없으니까 값 타입을 쓰거나, 복사가 필요해서 값 타입을 쓰는 등 보통은 단순한 이유에서 타입을 선택하기도 한다. 이에 본 세션에서는 나름 이유를 가지고 타입을 선택할 수 있도록 도움을 주고 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 타입을 선택하는 기준&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/LbR4kGN.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 모델링과 성능을 고려하여 타입을 선택할 것을 이야기하고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 모델링은 제쳐놓고 &lt;code&gt;성능&lt;/code&gt;에 대해서만 이야기를 나눠본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 이야기 할 부분은 총 세 가지로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Allocation
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 할당에 관한 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reference Counting
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조 관리에 관한 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Method Dispatch
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 메서드가 실행되어야하는지를 알아내는 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지 관점에서 타입의 성능을 판단하게 되는데 그림으로 보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/AT26ynj.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Allocation&lt;/code&gt;의 경우 stack 영역에 메모리가 할당되는지, heap 영역에 할당되는지 여부를 나타낸다. &lt;code&gt;Reference Counting&lt;/code&gt;은 참조 관리의 빈도에 대해서 나타내며, &lt;code&gt;Method Dispatch&lt;/code&gt;의 경우 정적(static)인지, 동적(dynamic)인지를 나타낸다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Allocation&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 할당(Allocation)의 경우, Stack과 Heap 두 가지로 나눠서 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Stack에 대해서 간략하게 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-1. Stack&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack 영역의 경우 &lt;code&gt;컴파일 타임&lt;/code&gt;에 그 크기가 정해지며, 주로 지역변수, 매개변수, 반환값 등이 저장되는 공간이다. 컴파일 타임에 크기가 결정되다 보니, heap 영역과 달리 동적인 할당/해제가 불가능하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 CPU에 의해 관리되어 heap 영역에 비해 &lt;b&gt;빠른 속도&lt;/b&gt;를 가진다는 장점이 있다. 반면에 메모리 크기에 제한이 있다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack의 경우 메모리를 할당하기 위해 stack 포인터를 감소시키고, 메모리 해제하기 위해 stack 포인터를 증가시키게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/max/1400/1*fwkyPI8Gmzd0Q_XAGM5_eA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack의 경우 높은 메모리 주소로부터 낮은 메모리 주소의 방향으로 채워지게 되는데, 앞서 말한 할당/해제에 따른 포인터의 변화가 이와 맥락을 같이 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, stack 메모리에 할당하는 경우 점점 더 낮은 메모리 주소에 가까워지는 것이기에, stack 포인터 또한 낮은 메모리 주소를 가리켜야 한다. 그러기 위해 &quot;감소(decrement)&quot;시킨다는 말을 사요하는 것이며, 당연히 그 반대는 증가(increment)가 될 것이다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-2. Heap&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 영역의 경우 사용자의 동적 할당에 기반하여 그 크기가 &lt;code&gt;런타임&lt;/code&gt;에 결정된다. 위에 그림을 봤듯이 stack과 달리 낮은 메모리 주소에서 높은 메모리 주소의 방향으로 메모리를 차지하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 heap 영역에는 클래스의 인스턴스나 클로저 같은 참조 타입이 저장된다. 즉, 참조 카운트(reference count)를 가지는 타입들이 온다는 것이기에, ARC의 대상들이 저장되는 공간이라고도 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 달리 메모리에 제한이 없다는 장점은 있지만, 메모리 할당/해제 작업으로 인한 속도가 저하된다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 영역의 경우 메모리에 할당하기 위해 사용하지 않은 빈 메모리 블록을 찾아야하는 비용이 있다. 또한 해제를 위해 메모리 블록을 다시 삽입해야하는 과정이 존재한다. 그리고 동적으로 할당/해제가 되기 때문에 thread-safe를 보장해주기 위한 overhead까지도 존재한다. (이렇게 보면 단점 투성이...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 stack과 heap에 대해서 봤으니, 이에 저장되는 대표적인 타입인 struct와 class가 어떻게 저장되고 관리되는지 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-3. Struct와 Class의 Allocation&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 struct의 경우를 살펴보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-3-A. Struct&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/HzoY27E.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;Point&lt;/code&gt;라는 struct 타입의 인스턴스를 하나 만들고, 복사한 모습을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 line-by-line으로 어떻게 stack 영역에 할당되는지 살펴볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/wqYQbNO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;point1&lt;/code&gt; 인스턴스를 생성하게 되면 stack 영역에 &lt;code&gt;point1&lt;/code&gt;의 메모리가 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/3ihYnLC.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 &lt;code&gt;point1&lt;/code&gt;을 복사하여 &lt;code&gt;point2&lt;/code&gt;를 생성하게 되면 이 때도 마찬가지로 &lt;code&gt;point2&lt;/code&gt;를 메모리에 할당하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/R3xpq8W.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고는 &lt;code&gt;point2.x = 5&lt;/code&gt;를 실행하는데, 값 타입이기에 &lt;code&gt;point2.x&lt;/code&gt; 값만 변화한 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아는 값 타입의 특징을 매우 잘 보여주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/wckbtKJ.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;point1&lt;/code&gt;과 &lt;code&gt;point2&lt;/code&gt;를 모두 사용하고 나면 메모리에서 해제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 class는 어떻게 동작할까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-3-B. Class&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Qju4NW0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진에 stack 영역이 있다는 것에 의아할 수 있다. 참조 타입은 heap 영역에 저장된다더니... 뒤통수 맞은 기분일 수 있겠지만.. 우선 heap 영역에 할당되는 것은 맞지만, stack에 heap을 가리키는 주소를 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/J6ffPPw.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;point1&lt;/code&gt; 인스턴스를 생성하면, 앞선 설명처럼 heap에 그 데이터가 저장되지만, heap을 가리키는 주소가 stack에 저장됨을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/F2sYA96.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 &lt;code&gt;point1&lt;/code&gt;을 복사하여, &lt;code&gt;point2&lt;/code&gt;를 생성해주면 참조 타입이기 때문에 동일한 heap 메모리 주소를 가리키게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/UBtdZTx.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 메모리 주소를 가리키고 있었기 때문에 프로퍼티의 값을 변경하게 되면 &lt;code&gt;point1&lt;/code&gt;, &lt;code&gt;point2&lt;/code&gt;의 &lt;code&gt;x&lt;/code&gt; 프로퍼티 값의 변경이 일어나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class의 특징을 잘 보여주고 있는 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/pY1Z5jD.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;point1&lt;/code&gt;, &lt;code&gt;point2&lt;/code&gt;를 모두 사용하고 나면 heap 영역에서 해제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/O0n4bwC.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후에 heap 주소를 가리키고 있던 stack 또한 해제되게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-3-C. 성능&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그림으로 성능을 살펴보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Uymbkgx.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class는 동적으로 할당/해제가 되기 때문에 많은 비용이 소모된다. 이에 위 그림처럼 나타낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/lBavbhp.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Struct의 경우 stack영역에 저장되며, 메모리 할당/해제에 별 다른 과정이 없기 때문에 위 그림처럼 나타낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 &lt;code&gt;Struct&lt;/code&gt;가 &lt;code&gt;Class&lt;/code&gt;보다 Allocation 측면에서는 성능적으로 더 우월하다는 것이다!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-3-D. 실제 개선 사례&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구구절절 이야기한 것 처럼 struct가 좋고, class가 allocation 관점에서 좋지 않다는 것을 봤으니 실제 필드에서는 어떤식으로 사용되는지 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/v2GHrex.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Color와 Orientation, Tail을 가지고 말풍선을 만드는 예시이다. 만든 말풍선을 불필요하게 다시 만드는 일을 없도록 하기 위해 &lt;code&gt;cache&lt;/code&gt;를 만드는데 key값이 string이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;String&lt;/code&gt;의 경우 struct로 구현되어있지만, 크기가 동적으로 변하기 때문에 컴파일 타임에 그 크기를 알 수 없어 heap 영역을 사용한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 String을 사용하는 것을 개선해볼 수 있는데, String이 아니라 아예 타입을 만들어서 해결해볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/PFyFCby.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;Attributes&lt;/code&gt;라는 struct를 만드는데, 프로퍼티들은 각각 enum으로 구현되어있다. 이에 &lt;code&gt;String&lt;/code&gt; 사용을 피할 수 있게 되는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Reference Counting&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;뿐만 아니라, 여러 쓰레드에서 참조 카운트를 올리고/내릴 수 있기 때문에 thread-safe를 보장하기 위한 오버헤드가 발생생할 수 있으며, 참조 카운트를 관리하기 위한 간접적인 단계가 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. Class&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 봤던 &lt;code&gt;Point&lt;/code&gt; 예제를 참조 카운트 관점에서 다시 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/1lg5svV.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARC 이전에는 직접적으로 참조 카운트를 올려주고 내려주기 위해서 retain/release를 해줬다고 한다(MRC). 하지만 ARC가 등장한 이후부터는 자동으로 이를 해주게 되었는데, 실제로는 눈에 보이지 않는 코드가 작동하고 있다고 봐도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 코드를 보면 인스턴스를 할당하고, 사용을 완료하는 그 사이사이에 retain/release가 들어가 있는 것을 볼 수 있고 &lt;code&gt;Point&lt;/code&gt; 타입 내에 참조 카운트가 프로퍼티로 존재하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/yuYZkbO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 &lt;code&gt;Point1&lt;/code&gt; 인스턴스가 생기면 refCount는 1만큼 상승한 1의 값을 가지게 되며 메모리에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/9mVMmHp.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;code&gt;Point1&lt;/code&gt; 를 복사하여 &lt;code&gt;Point2&lt;/code&gt; 인스턴스가 생기면 이 때도 마찬가지로 retain이 실행하기에 refCount는 1만큼 더해진 2가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/DdTekXH.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 &lt;code&gt;Point1&lt;/code&gt;을 다 사용하게 되면 release가 되면서 refCount 또한 1만큼 줄어들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/ddyrfi3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Point2&lt;/code&gt;도 마찬가지로 사용을 마치면 refCount를 내려주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARC에서 봤듯이 refCount가 0이 되면, 더 이상 필요하지 않은 것으로 인식하고 메모리에서 해제시킨다고 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/9FjqKdD.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 다음과 같이 stack/heap 영역에서 모두 매모리 해제되는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2. Struct&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Struct 타입은 기본적으로 참조를 가지지 않기 때문에 참조 카운트 또한 가지지 않는다. 하지만 Struct가 내부적으로 reference를 가지는 경우가 존재하는 데, 이 때는 당연히 참조 카운트를 보유하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/v7Jj9pR.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 경우 struct 타입이지만 reference를 가지게 된다. 왜냐하면 &lt;code&gt;text&lt;/code&gt;는 heap에 할당될 수 있는&lt;code&gt;String&lt;/code&gt; 타입이며, &lt;code&gt;UIFont&lt;/code&gt; 또한 &lt;code&gt;class&lt;/code&gt;이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;label1&lt;/code&gt; 인스턴스를 생성하는 시점에 참조가 하나 생기게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/LPe27zK.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 &lt;code&gt;label1&lt;/code&gt;을 복사해서 &lt;code&gt;label2&lt;/code&gt; 인스턴스를 만들 때도 이전에 생성된 주소를 가리키는 참조가 생기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/R8SN4Hh.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 retain/release가 되는 걸 코드로 보면 위와 같다. &lt;code&gt;label1&lt;/code&gt; 을 복사하게 되면 프로퍼티 두 개 모두 레퍼런스 카운트가 증가되는 것을 볼 수 있다. 그리고 사용을 마치면 모든 프로퍼티들이 참조 카운트가 1만큼 줄어드는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 알아차릴 수 있는 것은 struct내 참조 타입이 존재할 경우 복사 시에는 매번 모든 프로퍼티의 참조 카운트가 증가/감소한다는 것이다. 이에 유추해보면, 참조 타입의 프로퍼티가 많을 수록 성능이 떨어질 것이라는 것도 유추해볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-3. 성능&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/wLdn2x1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class의 경우 참조 카운트를 사용하기 때문에 위와 같이 나타난 것을 확인해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/jUpDYxO.png&quot; alt=&quot;&quot; /&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 reference를 가지는 struct의 경우는 어떨까?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/ZcgGIC9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음.. struct와 class의 혼종같은 느낌이다.. 앞서 이야기했던 것 처럼 reference 타입을 많이 가지게 되면 좋지 않을 것 같았는데,,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/QmICWwX.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 그림으로 보여주고 있다. 추측했던 것 처럼 struct내 reference 타입이 많아질 경우 성능이 더더욱 안좋아진다는 것을 알 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-4. 실제 개선 사례&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 타입을 가지는 struct의 예시를 보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/CtjB0Ky.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 말풍선 생성 예제처럼, &lt;code&gt;String&lt;/code&gt;이 문제가 되고 있다. 과연 이를 어떻게 개선해볼 수 있을까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Za7PkVF.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 애플이 제공해주는 &lt;code&gt;UUID&lt;/code&gt; 타입을 활용함으로써 이를 해결할 수 있다. &lt;code&gt;UUID&lt;/code&gt;는 총 36개의 문자로 이루어져있기 때문에 고정적인 길이를 가지는 String이다. 이에 이 경우 heap에 할당되는 것이 아니라 stack 영역에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/cumAoiI.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;code&gt;mimeType&lt;/code&gt; 또한 &lt;code&gt;String&lt;/code&gt;이다. 이는 현재 extension을 통해 구현되어 있는데 이 부분은 어떻게 개선해볼 수 있을까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/TwgcUXp.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 enum 타입을 만들어서 rawValue를 사용해보는 방식을 사용해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/XphdfEG.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 &lt;code&gt;URL&lt;/code&gt;을 제외한 두 프로퍼티는 reference를 가지지 않도록 만들어 준 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;code&gt;URL&lt;/code&gt;은 왜 안바꿔주는가??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마땅한 대체재가 없기 때문이라고 생각한다... 모든 것을 struct로 바꿔서 성능상의 이점을 가져가면 좋겠지만, 이렇게 현실과의 타협이 필요한 부분도 존재하는 것 같다. 이에 &lt;code&gt;URL&lt;/code&gt;만이 reference를 가지는 형상으로 개선해볼 수 있다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Method Dispatch&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 method dipatch란 무엇일까? 직역해보면 메서드를 붙인다(?)라고도 볼 수 있겠다. 추측을 해보면 메서드가 어디에 붙여질지? 정해주는 역할을 하지 않을까 생각해볼 수 있다.&lt;/p&gt;
&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;이 또한 종류가 static/dynamic 두 가지로 나뉘는데 하나씩 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. Static Method Dispatch&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;&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;이에 인라이닝(inlining)이나 다른 최적화를 기대해볼 수 있다.&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;위키를 살펴보면 &quot;함수를 함수라는 이름의 몸체의 콜 사이트로 대체하는 수동 또는 컴파일러 최적화 기법이다&quot;라고 설명하고 있다.&lt;/p&gt;
&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;쉽게 이야기해보자면, 함수 내에 다른 함수가 있는 경우 일반적으로는 이를 실행하기 위해 메모리 주소를 왔다갔다하며 실행하지만 인라이닝의 경우 이러한 비용없이 컴파일 타임에 대상되는 코드를 붙여서 실행해버리는 경우를 의미한다. 이에 call stack의 오버헤드가 줄어들며, 컴파일러의 추가 최적화가 가능해지는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 어려울 수 있지만... 코드를 보면 이해가 갈 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/jxTtuYj.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라이닝의 예시를 위해 위 코드를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;code&gt;drawAPoint(point)&lt;/code&gt;가 호출되고 있는 것을 알 수 있다. 그러면 우리는 이 메서드가 어떤 역할을 하는지 알아보기 위해 바로 구현부를 찾기 시작할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보면 바로 위에 &lt;code&gt;drawAPoint()&lt;/code&gt;가 구현되어 있는 것을 볼 수 있다. 구현부를 보니 &lt;code&gt;Point&lt;/code&gt; 타입을 파라미터로 받고, 내부에서 &lt;code&gt;.draw()&lt;/code&gt;를 호출하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠... 그러면 또 찾아가본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보아하니 &lt;code&gt;Point&lt;/code&gt; 타입 내에 인스턴스 메서드 &lt;code&gt;draw()&lt;/code&gt;가 있는 것을 볼 수 있다. 내부에는 구현부에 대한 내용이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 우리가 살펴본 과정에 의거하면 &lt;code&gt;drawAPoint(point)&lt;/code&gt;는 &lt;code&gt;draw()&lt;/code&gt;내 구현부로 대체될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Te2ivRU.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위 처럼 나타낼 수 있는 것이다. 이게 바로 &quot;인라이닝&quot;이다! 나름 더 쉽게 말하자면 메서드의 최상위로 이동하는 느낌인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 static dispatch의 경우 어떤 메서드의 구현부가 오게 될지 알고있다보니 앞서 말한 것 처럼 컴파일러 최적화나, 인라이닝의 장점을 얻을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. Dynamic Method Dispatch&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;&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;이 경우 런타임마다 v-table을 통해 메서드 구현부를 찾게 된다. 그 이후 구현부로 이동하여 실행하게 되는 것이다. 이 때문에 인라이닝이나 추가적인 다른 최적화가 어려워진다.(&lt;code&gt;private&lt;/code&gt;, &lt;code&gt;final&lt;/code&gt;을 사용해서 상속되지 않음을 나타내면 static dispatch를 강제할 수 있긴 하다! 이는 다음에 쓸 글에서 살펴보자...!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V-Table이란 수직 상속 관계를 포함하는 테이블로, 메서드 오버라이딩에 따라 실행 시점에 어떤 메서드를 실행할 지 결정하는 dynamic diapatch를 지원하기 위해 사용되는 일련의 메커니즘이다.&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;위에 static을 보면 쉽게쉽게 메서드 구현부를 다 잘 찾아가는 것 같은데 dynamic은 왜 필요한걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 상속으로 인한 &lt;code&gt;다형성&lt;/code&gt; 때문이다!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/XxrMjxt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속을 하면 메서드를 재정의 할 수 있게 되는데 이 때 각 메서드들의 구현부가 달라지기 때문에 컴파일러 입장에서는 어떤 메서드 구현부로 이동해야하는지 애매한 상황이 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼, &lt;code&gt;d.draw()&lt;/code&gt;에서는 어떤 &lt;code&gt;draw()&lt;/code&gt;의 구현부가 실행될지 모르는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/KltZ5eo.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for문을 그림으로 풀어서 보면 위처럼 될 것이다. 처음에 해당하는 &lt;code&gt;d[0]&lt;/code&gt;은 &lt;code&gt;Line&lt;/code&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;973&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkbYxP/btrq23Dud67/4KeRgEOdmd5xta6lNO9hsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkbYxP/btrq23Dud67/4KeRgEOdmd5xta6lNO9hsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkbYxP/btrq23Dud67/4KeRgEOdmd5xta6lNO9hsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkbYxP%2Fbtrq23Dud67%2F4KeRgEOdmd5xta6lNO9hsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;973&quot; height=&quot;622&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 V-Table을 통해 어떤 메서드의 구현부가 실행될지 알게 되고, 실행까지 이어질 수 있다. 그래서 이제는 &lt;code&gt;Line&lt;/code&gt;&lt;br /&gt;의 &lt;code&gt;draw()&lt;/code&gt; 메서드를 호출해 줄 수 있는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. 성능&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUTs7I/btrq334koOB/FOoKdPdpNrk2GG1ExHyhk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUTs7I/btrq334koOB/FOoKdPdpNrk2GG1ExHyhk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUTs7I/btrq334koOB/FOoKdPdpNrk2GG1ExHyhk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUTs7I%2Fbtrq334koOB%2FFOoKdPdpNrk2GG1ExHyhk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;977&quot; height=&quot;555&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합적으로 보면 class는 영 꽝인 것을 알 수 있다.&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;969&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y6Plu/btrq1gXDTjE/xyyBzxRUnE2FVJ5W3z3eqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y6Plu/btrq1gXDTjE/xyyBzxRUnE2FVJ5W3z3eqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y6Plu/btrq1gXDTjE/xyyBzxRUnE2FVJ5W3z3eqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY6Plu%2Fbtrq1gXDTjE%2FxyyBzxRUnE2FVJ5W3z3eqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;969&quot; height=&quot;579&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 앞서 말한 것 처럼 &lt;code&gt;final Class&lt;/code&gt;를 사용하면 상속이 불가하다는 것을 알려주기 때문에 강제적으로 static dispatch를 사용하여 이점을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 상속할 일이 없다면 &lt;code&gt;final&lt;/code&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;952&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGVfkT/btrq4vzss3J/dI2uW9iCbWEUsywjyQUIc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGVfkT/btrq4vzss3J/dI2uW9iCbWEUsywjyQUIc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGVfkT/btrq4vzss3J/dI2uW9iCbWEUsywjyQUIc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGVfkT%2Fbtrq4vzss3J%2FdI2uW9iCbWEUsywjyQUIc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;503&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;allocation, reference counting, method dispatch 어느 하나 빠질 것 없이 좋은 성능을 보인다. 이러니까 별 목적이 없다면 struct를 기본적으로 쓸 것을 애플이 권장하는 것 같다. 그러니까 우리도 타입을 선택할 때 이러한 성능들을 생각하고 선택하는 습관을 가져야 할 것 같다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤의 protocol types와 generics는 다음에 다뤄보도록 하자!&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ref&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;a href=&quot;https://developer.apple.com/videos/play/wwdc2016/416/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/videos/play/wwdc2016/416/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642435994448&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;Understanding Swift Performance - WWDC16 - Videos - Apple Developer&quot; data-og-description=&quot;In this advanced session, find out how structs, classes, protocols, and generics are implemented in Swift. Learn about their relative...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2016/416/&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2016/416/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pjWHq/hyM7X8veF2/8hkM1NXVCW868w7tRo3aF1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2016/416/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2016/416/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pjWHq/hyM7X8veF2/8hkM1NXVCW868w7tRo3aF1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Understanding Swift Performance - WWDC16 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In this advanced session, find out how structs, classes, protocols, and generics are implemented in Swift. Learn about their relative...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642435987809&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;스위프트 타입별 메모리 분석 실험&quot; data-og-description=&quot;struct와 class 가 메모리 영역을 어디를 사용하는지 분석한 실험 결과&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee&quot; data-og-url=&quot;https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cwXzp8/hyM6Dw3vXD/LiGqTzSr9GPA5PgtpUGi70/img.png?width=1200&amp;amp;height=531&amp;amp;face=0_0_1200_531,https://scrap.kakaocdn.net/dn/QUq5u/hyM71bZoi3/nUCKxFTKRNGuWKLJlliWDK/img.png?width=60&amp;amp;height=41&amp;amp;face=0_0_60_41,https://scrap.kakaocdn.net/dn/bac8pV/hyM6Dw3vXb/nLa4zVxDaxxvUZ3rIoqKUK/img.png?width=60&amp;amp;height=36&amp;amp;face=0_0_60_36&quot;&gt;&lt;a href=&quot;https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cwXzp8/hyM6Dw3vXD/LiGqTzSr9GPA5PgtpUGi70/img.png?width=1200&amp;amp;height=531&amp;amp;face=0_0_1200_531,https://scrap.kakaocdn.net/dn/QUq5u/hyM71bZoi3/nUCKxFTKRNGuWKLJlliWDK/img.png?width=60&amp;amp;height=41&amp;amp;face=0_0_60_41,https://scrap.kakaocdn.net/dn/bac8pV/hyM6Dw3vXb/nLa4zVxDaxxvUZ3rIoqKUK/img.png?width=60&amp;amp;height=36&amp;amp;face=0_0_60_36');&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;스위프트 타입별 메모리 분석 실험&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;struct와 class 가 메모리 영역을 어디를 사용하는지 분석한 실험 결과&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.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> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <category>Swift 성능 이해하기</category>
      <category>Understanding Swift Performance</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/557</guid>
      <comments>https://leechamin.tistory.com/557#entry557comment</comments>
      <pubDate>Tue, 18 Jan 2022 01:13:44 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Modern Collection View - Grid 구현</title>
      <link>https://leechamin.tistory.com/556</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Modern Collection View - Grid 구현&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 글(&lt;a href=&quot;https://leechamin.tistory.com/555#Modern%--Collection%--View%--%EA%B-%AC%ED%--%--&quot;&gt;[iOS] Modern Collection View - List 구현&lt;/a&gt;)에서는 collection View를 활용하여 list 형태의 뷰를 구현하는 방식을 봤었다. 이제는 원래 collection View의 목적이라고도 할 수 있는(?) grid(격자)형태의 뷰를 구성해보자.&lt;br /&gt;(List 형태를 구현할 때와 코드가 거의 유사하니 이전 글을 참고해도 좋을 것 같다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 또한 iOS 14이후의 버전에 대해 지원해주는 modern한 구현 방식이 있는데, 이를 알아보기 전에 기존 방식으로는 어떻게 구현했는지 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기존 collectionView 구현 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 그릴 화면을 먼저 보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/zcGfv5V.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔히 볼 수 있는 grid 형태의 뷰이다. 이를 구현하기 위해서는 어떠한 순서대로 구현하면 좋을지 먼저 살펴보자. 구현 방식에는 개개인마다 차이가 있다는 점을 유의하면서 보면 좋을 것 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. UICollectionView를 생성해둔다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; 1-1. 이 때 클로저 방식으로 생성하면서 속성들을 모두 부여해준다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;1-2. 기본적으로 UICollectionViewFlowLayout으로 기본적인 레이아웃을 잡아준다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;1-3. 직접 만든 cell을 등록시켜준다.&lt;br /&gt;2. UICollectionViewCell을 생성한다.&lt;br /&gt;3. (데이터 관리) &lt;code&gt;UICollectionViewDataSource&lt;/code&gt;를 &lt;code&gt;ViewController&lt;/code&gt;에 채택한다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;3-1.&amp;nbsp; (개수)&lt;code&gt;collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&amp;gt; Int&lt;/code&gt; 메서드를 통해 한 섹션에 몇 개의 아이템이 올지 정해준다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;3-2. (데이터) &lt;code&gt;collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&amp;gt; UICollectionViewCell&lt;/code&gt; 메서드를 통해 cell을 dequeue해주고, cell에 데이터를 할당하고, 꾸며준다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;3-3. 커스텀한 레이아웃을 위해 &lt;code&gt;UICollectionViewDelegateFlowLayout&lt;/code&gt;를 채택해주고, &lt;code&gt;collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&amp;gt; CGSize&lt;/code&gt;메서드를 통해 item별 사이즈를 정해준다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부적으로 스텝을 구분해보긴 했지만 크게 보면 3가지 부분으로 볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;UICollectionView의&amp;nbsp;데이터&lt;br /&gt;2.&amp;nbsp;UICollectionView의&amp;nbsp;레이아웃&lt;br /&gt;3.&amp;nbsp;UICollectionViewCell&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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;ViewController코드 더보기&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;pre class=&quot;reasonml&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class AnimalViewController: UIViewController {
    let api = APIService()
    var animalData: [Animal] = []
    let collectionView: UICollectionView = {
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .vertical

        let cv = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        cv.register(AnimalCollectionViewCell.self, forCellWithReuseIdentifier: AnimalCollectionViewCell.identifier)
        cv.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        return cv
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = &quot;ZOO&quot;
        view.backgroundColor = .white
        getData()
    }

    func getData() {
        api.getAnimalData { result in
            switch result {
            case .success(let animals):
                self.animalData = animals
                DispatchQueue.main.async {
                    self.setupCollectionView()
                }
            case .failure(let error):
                print(error)
            }
        }
    }

    func setupCollectionView() {
        collectionView.dataSource = self
        collectionView.delegate = self

        view.addSubview(collectionView)

        collectionView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])

    }
}


extension AnimalViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&amp;gt; Int {
        return animalData.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&amp;gt; UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: &quot;cell&quot;, for: indexPath) as? AnimalCollectionViewCell else {
            return AnimalCollectionViewCell()
        }

        let data = animalData[indexPath.item]
        cell.configCell(with: data)
        return cell
    }
}

extension AnimalViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&amp;gt; CGSize {
        let margin: CGFloat = 10
        return CGSize(width: (self.view.frame.width - (margin * 3)) / 2, height: self.view.frame.height / 4)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;AnimalCollectionViewCell 코드 더보기&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;pre class=&quot;roboconf&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class AnimalCollectionViewCell: UICollectionViewCell {
    static let identifier = &quot;cell&quot;

    let nameLabel: UILabel = {
        let lb = UILabel()
        lb.textColor = .white
        lb.textAlignment = .right
        lb.numberOfLines = 0
        return lb
    }()

    let animalImage: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFill
        return iv
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        layout()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    func layout() {
        [animalImage, nameLabel].forEach {
            contentView.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        layer.masksToBounds = true
        layer.cornerRadius = 10

        NSLayoutConstraint.activate([
            animalImage.topAnchor.constraint(equalTo: contentView.topAnchor),
            animalImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            animalImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            animalImage.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),

            nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            nameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
        ])
    }

    func configCell(with animal: Animal) {
        ImageLoader.loadImage(from: animal.imageLink) { image in
            self.animalImage.image = image
        }

        let attrString = NSAttributedString(
            string: animal.name,
            attributes: [
                NSAttributedString.Key.strokeColor: UIColor.black,
                NSAttributedString.Key.foregroundColor: UIColor.white,
                NSAttributedString.Key.strokeWidth: -2.0,
                NSAttributedString.Key.font: UIFont(name: &quot;Helvetica-Bold&quot;, size: 30)
            ]
        )

        nameLabel.attributedText = attrString
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 방식은 잘 알 것이라고 생각한다...!&lt;br /&gt;(사실 비교를 위해 기존 방식의 코드를 넣은거긴 하니까... 세세한 설명들은 스킵...!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List를 구현할 때는 전용 cell을 만들어서 사용했지만, 다행인 건 grid 형태를 구현할 때는 modern 방식에서도 기존 cell을 그대로 사용할 수 있다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Modern CollectionView - Grid 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 본 목적인 modern하게 grid형태의 collection view를 그리는 방법을 살펴보자.&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.&amp;nbsp;UICollectionView&amp;nbsp;타입의&amp;nbsp;프로퍼티&amp;nbsp;정의&lt;br /&gt;2.&amp;nbsp;UICollectionViewDiffableDataSource&amp;nbsp;타입의&amp;nbsp;프로퍼티&amp;nbsp;정의&lt;br /&gt;3.&amp;nbsp;UICollectionViewCompositionalLayout&amp;nbsp;레이아웃&amp;nbsp;생성&lt;br /&gt;4.&amp;nbsp;UICollectionView&amp;nbsp;인스턴스&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;레이아웃&amp;nbsp;적용&lt;br /&gt;5.&amp;nbsp;UICollectionViewDiffableDataSource&amp;nbsp;구현&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5-1.&amp;nbsp;CellRegistration&amp;nbsp;구현&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5-2.&amp;nbsp;UICollectionViewDiffableDataSource&amp;nbsp;인스턴스&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;cellProvider의&amp;nbsp;dequeueConfiguredReusableCell&amp;nbsp;구현&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5-3.&amp;nbsp;NSDiffableDataSourceSnapShot&amp;nbsp;생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. &lt;code&gt;UICollectionView&lt;/code&gt; 타입의 프로퍼티 정의&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;var gridCollectionView: UICollectionView!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 뭐..!&lt;br /&gt;(사실 암시적 추출 옵셔널보다는 옵셔널(&lt;code&gt;?&lt;/code&gt;)을 사용하는 것이 더 안전하지만 편의를 위해 암시적 추출 옵셔널을 사용하여 진행합니다!)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. &lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt; 타입의 프로퍼티 정의&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;nsis&quot;&gt;&lt;code&gt;var dataSource: UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 section과 데이터 타입을 명시해줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. &lt;code&gt;UICollectionViewCompositionalLayout&lt;/code&gt; 레이아웃 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;item -&amp;gt; group -&amp;gt; section&lt;/code&gt; 순서대로 생성해주고 section을 사용하여 &lt;code&gt;UICollectionViewCompositionalLayout&lt;/code&gt; 인스턴스를 하나 만들어 반환해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서서 먼저 item/group/section 그리고 이들의 사이즈를 정해주기 위한 &lt;code&gt;NSCollectionLayoutSize&lt;/code&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;code&gt;NSCollectionLayoutSize&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;collection view내 요소의 너비와 높이를 나타낸다.&lt;/li&gt;
&lt;li&gt;width/height diemension을 통해 크기를 줄 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.fractionalWidth&lt;/code&gt;: 포함하는 그룹 너비에 상대적인 크기이다. &lt;code&gt;1.0&lt;/code&gt; 이라는 것은 동일한 크기를 주겠다는 것. 1보다 큰 수를 주면 그 배수만큼 값이 적용된다. &lt;code&gt;0.2&lt;/code&gt;를 적으면 20%만큼의 크기를 가지겠다는 의미가 된다!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.fractionalHeight&lt;/code&gt;: 위의 높이 버전!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.estimated()&lt;/code&gt; : 데이터가 로딩되거나 응답이 바뀌는 경우와 같이 런타임에 컨텐츠의 크기가 변경될 수 있기에 추정값을 넣어준다. 초기 추정 크기를 제공하면 시스템은 실제 값을 나중에 연산한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.absolute()&lt;/code&gt;: 절대적인 크기를 입력하여 사용하는 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;code&gt;NSCollectionLayoutItem&lt;/code&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/fw8P4pY.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;collection view 레이아웃에서 가장 기본이 되는 구성 요소이다.&lt;/li&gt;
&lt;li&gt;item은 collection view내 컨텐츠의 개별적인 공간의 배열이나 공간, 크기를 나타내는 청사진이다. item은 스크린에 보여질 단일 뷰이며, 일반적으로 cell이라고 봐도 된다. 하지만 item은 헤더, 푸터, 데코뷰가 될 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;code&gt;NSCollectionLayoutGroup&lt;/code&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/YdOrKTf.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방향에 따라 item을 배치하는 item들의 집합의 컨테이너이다.&lt;/li&gt;
&lt;li&gt;그룹은 collection view의 item이 서로간에 어떻게 배치되는지를 결정한다. 그룹은 item을 horizontal/vertical 혹은 커스텀하게 배치할 지 결정할 수 있다. 그룹은 item들이 렌더링되는 방법에 대한 규칙을 정하지만 스스로(그룹)는 내용을 렌더링하지 않는다.(즉 아이템이 무조건 있어야 한다는 것 같다!)&lt;/li&gt;
&lt;li&gt;그룹은 &lt;code&gt;NSCollectionLayoutItem&lt;/code&gt;의 하위 클래스이기 때문에 아이템처럼 사용할 수 있다. 그룹을 다른 아이템들과 결합하거나 아래와 같이 그룹을 더 복잡한 레이아웃으로도 사용해볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/zkSTHbX.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;code&gt;NSCollectionLayoutSection&lt;/code&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/PyV45TN.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그룹들을 고유한 시각적 묶음으로 결합하는 컨테이너이다.&lt;/li&gt;
&lt;li&gt;즉 그룹들의 모음집이라고도 볼 수 있다.&lt;/li&gt;
&lt;li&gt;collection view의 레이아웃은 하나 이상의 섹션을 가진다. 섹션은 레이아웃을 서로 다른 조각으로 구분할 수 있는 방법을 제공한다.&lt;/li&gt;
&lt;li&gt;각 섹션은 collection view의 다른 섹션들과 동일한 레이아웃을 가질 수 있고, 다를 수도 있다. 섹션의 레이아웃은 그룹의 프로퍼티에 의해 결정된다.&lt;/li&gt;
&lt;li&gt;각 섹션은 다른 섹션과 구분하기 위해 배경, 헤더, 푸터를 가질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 코드로 하나하나 쌓아보면 이해가 더 잘 될 것이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func createLayout() -&amp;gt; UICollectionViewCompositionalLayout{
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(self.view.frame.height * 0.25))
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
    group.interItemSpacing = .fixed(10)

    let section = NSCollectionLayoutSection(group: group)
    section.interGroupSpacing = CGFloat(10)
    section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)

    let layout = UICollectionViewCompositionalLayout(section: section)

    return layout
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명한 것 처럼 item -&amp;gt; group -&amp;gt; section 순으로 쌓고 layout을 만드는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;UICollectionViewCompositionalLayout&lt;/code&gt; 인스턴스를 만들었으니 이를 활용하여 &lt;code&gt;UICollectionView&lt;/code&gt; 인스턴스를 생성해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. &lt;code&gt;UICollectionView&lt;/code&gt; 인스턴스 생성 및 레이아웃 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 만든 레이아웃을 활용하여 collection view에 적용하고 collection view의 레이아웃을 잡아주면 된다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func createGridCollectionView() {
    gridCollectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())

    view.addSubview(gridCollectionView)
    gridCollectionView.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        gridCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        gridCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        gridCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        gridCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    ])
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. &lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt; 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 data source를 다룰 시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 부분은 list를 구현할 때와 거의 사실상 동일하다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func configDataSource() {
    // 5-1. `CellRegistration` 구현
    let cellRegistration = UICollectionView.CellRegistration&amp;lt;AnimalCollectionViewCell, Animal&amp;gt; { cell, indexPath, animal in
        cell.configCell(with: animal)
    }

    // 5-2. `UICollectionViewDiffableDataSource` 인스턴스 생성 및 cellProvider의 `dequeueConfiguredReusableCell` 구현
    dataSource = UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;(collectionView: gridCollectionView, cellProvider: { (collectionView, indexPath, itemIdentifier) -&amp;gt; UICollectionViewCell? in
        return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
    })

    // 5-3. `NSDiffableDataSourceSnapShot` 생성 
    var snapShot = NSDiffableDataSourceSnapshot&amp;lt;Section, Animal&amp;gt;()
    snapShot.appendSections([.main])
    snapShot.appendItems(animalData)
    dataSource.apply(snapShot)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 크게 어렵지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cell을 등록해주고, cell을 반환해주고, cell에 들어갈 데이터를 snapshot을 통해 넣어주는 그러한 일련의 과정이다.&lt;br /&gt;각 메서드에 대한 자세한 설명은 &lt;a href=&quot;https://leechamin.tistory.com/555#Modern%--Collection%--View%--%EA%B-%AC%ED%--%--&quot;&gt;[iOS] Modern Collection View - List 구현&lt;/a&gt;를 참고하자.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 구현한 화면을 볼 시간!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/sRtvnPV.gif&quot; alt=&quot;&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;modern하게 grid collection view 구현하는 방법의 장점을 생각해보면 2가지 정도 있는 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직관적으로 grid내 계층을 그릴 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DiffableDataSource&lt;/code&gt;로 데이터와 UI를 쉽게 관리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 자체의 길이는 비슷비슷한 것 같지만 modern의 방식이 조금 더 코드의 흐름을 읽기에도 좋은 것 같다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 iOS 14 이상에서만 가능한 코드이지만... 계속 버전이 올라감에 따라 이 방법이 표준이 될 날도 얼마 남지 않을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간 SwiftUI에서 뷰를 하나하나 쌓은 것 처럼, UIKit을 가지고 유사하게 구현한(?) 느낌을 개인적으로 받았다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼! 지금까지 grid 형태의 collection view를 만드는 두 가지 방법을 봤다. 각자 생각하는 장단점에 의거해서 구현 방식을 선택하면 될 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;AnimalGridViewController 코드 더보기&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;pre id=&quot;code_1642171760938&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit


class AnimalGridViewController: UIViewController {
    enum Section {
        case main
    }
    
    let api = APIService()
    var animalData: [Animal] = []
    var gridCollectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = &quot;ZOO&quot;
        view.backgroundColor = .white
        getData()
    }
    
    func getData() {
        api.getAnimalData { result in
            switch result {
            case .success(let animals):
                self.animalData = animals
                DispatchQueue.main.async {
                    self.createGridCollectionView()
                    self.configDataSource()
                }
            case .failure(let error):
                print(error)
            }
        }
    }
    
    func createLayout() -&amp;gt; UICollectionViewCompositionalLayout{
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(2.0), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(2.0), heightDimension: .absolute(self.view.frame.height * 0.25))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
        group.interItemSpacing = .fixed(10)
        
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = CGFloat(10)
        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        
        return layout
    }
    
    func createGridCollectionView() {
        gridCollectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())
        
        view.addSubview(gridCollectionView)
        gridCollectionView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            gridCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            gridCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            gridCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            gridCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }
    
    func configDataSource() {
        let cellRegistration = UICollectionView.CellRegistration&amp;lt;AnimalCollectionViewCell, Animal&amp;gt; { cell, indexPath, animal in
            cell.configCell(with: animal)
        }
        
        dataSource = UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;(collectionView: gridCollectionView, cellProvider: { (collectionView, indexPath, itemIdentifier) -&amp;gt; UICollectionViewCell? in
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        })
        
        var snapShot = NSDiffableDataSourceSnapshot&amp;lt;Section, Animal&amp;gt;()
        snapShot.appendSections([.main])
        snapShot.appendItems(animalData)
        dataSource.apply(snapShot)
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끄읕&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views&quot;&gt;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views&lt;/a&gt;&lt;/p&gt;</description>
      <category> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <category>grid collectionview</category>
      <category>implement modern collectionview</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/556</guid>
      <comments>https://leechamin.tistory.com/556#entry556comment</comments>
      <pubDate>Fri, 14 Jan 2022 23:50:05 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Modern Collection View - List 구현</title>
      <link>https://leechamin.tistory.com/555</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Modern Collection View 구현&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 14 이후부터 지원하는 기능으로 이전의 방법과 아예 다른 방식으로 collection 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;이번에는 List 형태의 collection view를 만들어 볼 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/hg3XEw7.png&quot; alt=&quot;&quot; width=&quot;459&quot; height=&quot;993&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 뷰를 보면 &quot;테이블뷰로 만들었네!&quot;라고도 할 수 있지만 collection view로도 동일한 뷰를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 modern하게 collection view를 list 형식을 차근차근 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(전체 코드는 맨 아래에서 확인 가능하다)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;순서&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. CollectionView&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt; 만들기 (List)&lt;br /&gt;&lt;/span&gt;2. CollectionViewListCell 생성&lt;br /&gt;&amp;nbsp; &amp;nbsp; 2-1. UIConfigurationStateCustomKey 생성&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;2-2. UIConfigurationState를 extension하여 state의 데이터 프로퍼티 생성&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;2-3. ConfigurationState를 재정의하여 현재 상태의 cell이 가지고 있는 새로운 값을 위 프로퍼티에 넣어준다. 그 이후 리턴!&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;2-4. cell에서 사용할 UIListContentView를 생성한다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;2-5. cell내의 Layout을 잡아준다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;2-6. updateConfiguration(using state: UICellConfigurationState) 재정의&lt;br /&gt;3. &lt;code&gt;collectionView&lt;/code&gt;의 layout 생성 (&lt;code&gt;UICollectionViewCompositionalLayout&lt;/code&gt;)&lt;br /&gt;4. &lt;code&gt;collectionView&lt;/code&gt;에 레이아웃 적용하여 뷰에 추가&lt;br /&gt;5. dataSource에 &lt;code&gt;CellRegistration&lt;/code&gt;, &lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt; 할당&lt;br /&gt;6. &lt;code&gt;snapshot&lt;/code&gt;을 dataSource에 apply&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;6-1. 모델에 Hashable 프로토콜 채택 및 identifier 프로퍼티 생성&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. &lt;code&gt;CollectionView &lt;/code&gt;만들기 (List)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 ViewController에 collectionView를 만들어 놓고 대기시켜둔다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;var collectionView: UICollectionView!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. &lt;code&gt;CollectionViewListCell&lt;/code&gt; 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 custom한 &lt;code&gt;UICollectionViewListCell&lt;/code&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;2-1. &lt;code&gt;UIConfigurationStateCustomKey&lt;/code&gt; 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;code&gt;contifurationState&lt;/code&gt;에 추가할 뷰의 커스텀 state를 정의한다.&lt;br /&gt;이는 나중에 &lt;code&gt;UIConfigurationState&lt;/code&gt;에서 커스텀한 state를 만들기 위해 사용된다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;private extension UIConfigurationStateCustomKey {
    static let animal = UIConfigurationStateCustomKey(&quot;animal&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-2. &lt;code&gt;UIConfigurationState&lt;/code&gt;를 extension하여 state의 데이터 프로퍼티 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UIConfigurationState&lt;/code&gt;를 extension하여 위에서 만든 커스텀 state를 타입 프로퍼티로 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UIConfigurationState&lt;/code&gt; 프로토콜은 외관에 영향을 미치는 모든 일반적인 state와 함께 trait collection을 포함하는 configuration state 객체의 청사진을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;UIConfigurationState&lt;/code&gt; : 뷰의 상태(state)를 캡슐화하여 가지고 있는 객체&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get&lt;/code&gt; : 위에서 만든 custom key를 가지고 state를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set&lt;/code&gt; : 위의 키에 해당하는 value에 새로운 newValue를 할당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension UIConfigurationState {
    var animalData: Animal? {
        get { return self[.animal] as? Animal }
        set { self[.animal] = newValue }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3. Cell내에서 &lt;code&gt;ConfigurationState&lt;/code&gt;를 재정의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 상태의 cell이 가지고 있는 새로운 값을 위에서 만든 &lt;code&gt;animalData&lt;/code&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;code&gt;UICellConfigurationState&lt;/code&gt; : cell의 상태(state)를 캡슐화한 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cell의 상태라고 하면 selected, focused, disabled의 상태인지 등을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로는 &lt;code&gt;UICellConfigurationState&lt;/code&gt; 객체를 직접 생성해주지는 않는다. configuration state를 갖기 위해서는 cell의 하위 클래스에서 &lt;code&gt;updateConfiguration(using:)&lt;/code&gt; 메서드를 재정의하여 state 파라미터를 써주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드 밖에서는 configurationState 프로퍼티를 사용하여 cell의 configuration state를 얻을 수 있다. 앞서 말한 것 처럼 &lt;code&gt;UIConfigurationStateCustomKey&lt;/code&gt;를 사용하여 커스텀 state key를 정의하면 커스텀 state를 cell configuration state에 추가해줄 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class AnimalListCell: UICollectionViewListCell {
    private var animalData: Animal?   

    // ...

    override var configurationState: UICellConfigurationState {
        var state = super.configurationState
        state.animalData = self.animalData
        return state
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-4. cell에서 사용할 &lt;code&gt;UIListContentView&lt;/code&gt;를 생성한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 스타일은 &lt;code&gt;UIListContentConfiguration&lt;/code&gt;를 통해 정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 &lt;code&gt;defaultAnimalConfiguration()&lt;/code&gt; 메서드를 통해 정해주자!&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class AnimalListCell: UICollectionViewListCell {
    private var animalData: Animal?
    // list 기반의 contentView를 반환하는 메서드
    private func defaultAnimalConfiguration() -&amp;gt; UIListContentConfiguration {
        return .subtitleCell()
    }

    // list 기반의 contentView를 만든다
    private lazy var animalListContentView = UIListContentView(configuration: defaultAnimalConfiguration())    
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-5. cell내의 layout을 잡아준다.&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;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 정의한 &lt;code&gt;setupViewsIfNeeded()&lt;/code&gt;은 바로 다음에 볼 &lt;code&gt;updateConfiguration()&lt;/code&gt; 에서 호출하게 되는데, 업데이트 되면서 레이아웃이 여러 번 중복적으로 잡히지 않게 하기 위해 특정 레이아웃 집합을 따로 빼두어 nil인지 확인해준다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;extension AnimalListCell {
    func setupViewsIfNeeded() {
        // 커스텀 view에 대한 제약이 기존에 주어져있다면 다시 layout를 적용하지 않도록 함
        guard animalTypeConstraints == nil else {
            return
        }

        [animalListContentView, animalTypeLabel].forEach {
            contentView.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        let constraints = (leading:
                            animalTypeLabel.leadingAnchor.constraint(greaterThanOrEqualTo: animalListContentView.trailingAnchor),
                           trailing:
                            animalTypeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) )

        NSLayoutConstraint.activate([
            animalListContentView.topAnchor.constraint(equalTo: contentView.topAnchor),
            animalListContentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            animalListContentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            animalTypeLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            constraints.leading,
            constraints.trailing
        ])

        animalTypeConstraints = constraints
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-6. &lt;code&gt;updateConfiguration(using state: UICellConfigurationState)&lt;/code&gt; 재정의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 state를 사용하여 cell의 configuration을 업데이트해주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 메서드를 직접적으로 호출하는 것은 피해야하며 대신에 &lt;code&gt;setNeedsUpdateConfiguration()&lt;/code&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;code&gt;setNeedsUpdateConfiguration()&lt;/code&gt; : 현재 state로 configuration을 업데이트 할 것을 cell에게 알린다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cell의 configuration이 현재 state에 따라 업데이트 되어야 할 때 호출해줄 수 있다.&lt;/li&gt;
&lt;li&gt;시스템은 &lt;code&gt;configurationState&lt;/code&gt;가 바뀔 때 마다 이 메서드를 자동으로 호출해주며, 업데이트를 요청하는 다른 상황에서도 동일하다. 시스템은 여러 요청들을 통합하여 한 번의 업데이트를 한다.&lt;/li&gt;
&lt;li&gt;만약 cell의 configuration state에 커스텀 state를 추가했다면 커스텀 state가 바뀔 때 마다 매번 이 메서드가 불릴 수 있도록 해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 아래와 같이 하위 클래스(셀)에서 재정의하여 제공되는 state를 사용하여 cell의 configuration을 업데이트할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;override func updateConfiguration(using state: UICellConfigurationState) {
    setupViewsIfNeeded()

    var content = defaultAnimalConfiguration().updated(for: state)

    content.image = urlToImage(state.animalData?.imageLink ?? &quot;&quot;)
    content.imageProperties.maximumSize = CGSize(width: 50, height: 50)
    content.text = state.animalData?.name
    content.textProperties.font = .preferredFont(forTextStyle: .headline)
    content.secondaryText = &quot;평균 수명: \(state.animalData?.lifespan ?? &quot;&quot;)년&quot;

    animalListContentView.configuration = content

    animalTypeLabel.text = state.animalData?.animalType ?? &quot;1&quot;
}

func urlToImage(_ urlString: String) -&amp;gt; UIImage? {
    guard let url = URL(string: urlString),
          let data = try? Data(contentsOf: url),
          let image = UIImage(data: data) else {
              return nil
          }

    return image
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말한 것 처럼 cell의 configuration state에 커스텀 state를 추가했다면 커스텀 state가 바뀔 때 마다 매번 이 메서드가 불릴 수 있도록 해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 cell 내에 해당 부분을 구현해준다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func update(with newAnimalData: Animal) {
    guard animalData != newAnimalData else {
        return
    }
    animalData = newAnimalData
    setNeedsUpdateConfiguration()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드는 이후 collectionView에서 &lt;code&gt;CellRegistration&lt;/code&gt;의 handler에서 호출된다. 즉 이 때마다 cell을 업데이트 시켜주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ViewController로 넘어가보자!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. collectionView의 layout 생성 (UICollectionViewCompositionalLayout)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;UICollectionViewCompositionalLayout&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적응력이 높고 유연한 시각적 배열의 items를 결합할 수 있는 layout 객체다.&lt;/li&gt;
&lt;li&gt;이는 collectionview layout의 한 종류이다. 이는 유연하고 빠르며 각 작은 구성 요소를 전체 레이아웃으로 결합하여 컨텐츠에 대한 시각적인 배열을 구축할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compositional layout은 뚜렷한 시각적 그룹으로 나뉘는 하나 이상의 섹션을 가지고 있다. 각 섹션은 표시하려는 가장 작은 데이터 단위인 개별 items가 담긴 group으로 구성된다. group은 수평/수직 또는 커스텀하게 정할 수 있다. 아이템에서 그룹, 그룹에서 섹션으로 구성하다보면 전체 레이아웃을 구성할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;UICollectionLayoutListConfiguration&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;list layout을 만들기 위한 configuration이다.&lt;/li&gt;
&lt;li&gt;이 configuration을 사용하여 compositionalLayout에 대한 list section을 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;또한 layout은 오직 list secion만을 갖고 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 방법으로 list section을 가지는 compositional layout을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 긱 섹션에 대해 서로 다른 list configuration을 구현하고 싶다면 compositional layout의 section provider를 사용하여 각 섹션 별로의 list configuration을 만들면 된다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func createListLayout() -&amp;gt; UICollectionViewCompositionalLayout {    
    let config = UICollectionLayoutListConfiguration(appearance: .plain)
    return UICollectionViewCompositionalLayout.list(using: config)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. collectionView에 레이아웃 적용하여 뷰에 추가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 만든 collectionViewLayout을 추가하고, collectionView 인스턴스를 생성해주는 역할을 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func configureCollectionView() {
    collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createListLayout())
    view.addSubview(collectionView)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. dataSource에 CellRegistration, UICollectionViewDiffableDataSource 할당&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 dataSource가 있어야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var dataSource: UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 diffableDataSource를 만들어 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt;는 데이터를 관리하고 cell들을 collectionView에 제공해주는 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;diffableDataSource 객체는 collectionView 객체와 함께 작업하는 dataSource의 특정한 타입일 뿐이다. 주로 collection View의 데이터나 UI를 간편하고 효율적으로 업데이트 해주는 것을 관리하는 행위를 한다. 이 또한 마찬가지로 &lt;code&gt;UICollectionViewDataSource&lt;/code&gt; 프로토콜을 채택하고 있기에 프로토콜이 가지는 메서드는 모두 제공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collectionView에 데이터를 채우려면 다음의 순서를 따르면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. diffableDataSource를 collectionView에 연결한다.&lt;br /&gt;2. collection View의 cell을 구성하기 위해 cell provider를 구현한다.&lt;br /&gt;3. 데이터의 현재 state를 생성한다.&lt;br /&gt;4. 데이터와 UI를 나타낸다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;diffable data source를 collectionView에 연결하기 위해서는 &lt;code&gt;init(collectionView:cellProvider:)&lt;/code&gt;을 통해 diffableDataSource를 만들고 collectionView에 넘겨주면 된다. 이 때 cell provider도 넘겨주게 되는데, 데이터와 UI를 어떻게 cell에 보여줄 지를 구성하는 역할을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 데이터의 현재 state를 생성하고, data를 UI에 나타내고, snapshot에 applying 해준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;!!중요!! dataSource는 중간에 바꿔 끼우면 안된다!!&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;diffableDataSource를 구성한 이후로는 collectionView의 dataSource를 바꾸면 안된다. 만약 collection View가 첫 구성 이후에 새로운 data source를 필요로 한다면, 새로운 collectionView와 diffableDataSource를 구성하고 만들어야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 만들어둔 dataSource를 사용하여 &lt;code&gt;CellRegistration&lt;/code&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;code&gt;CellRegistration&lt;/code&gt; :
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;collection view에 cell을 등록해준다.&lt;/li&gt;
&lt;li&gt;collection View에 cell을 등록하고 각 cell을 보여주기 위해 구성한다.&lt;/li&gt;
&lt;li&gt;cell registration은 cell 타입과 data 타입을 generics 파라미터에 넣어준다.&lt;/li&gt;
&lt;li&gt;cell과 data를 registration handler로 넘겨서 cell을 구성해주는 것이다. handler에서는 content를 어떻게 구성할 지, cell의 외부모습은 어떻게 만들지 정해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let cellRegistration = UICollectionView.CellRegistration&amp;lt;AnimalListCell, Animal&amp;gt; { (cell, indexPath, animal) in
    cell.update(with: animal)
    cell.accessories = [.disclosureIndicator()]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cell registration 이후에는 &lt;code&gt;dequeueConfiguredReusableCell(using:for:item:)&lt;/code&gt;에 넘겨서 data source의 cell provider를 호출해준다. 이에 &lt;code&gt;register()&lt;/code&gt;를 해줄 필요가 없는 것이다. collectionView는 cell registration을 &lt;code&gt;dequeueConfiguredReusableCell(using:for:item:)&lt;/code&gt;에 넘겨주면 자동으로 cell을 등록해주게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;dataSource = UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) -&amp;gt; UICollectionViewCell? in
    return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dequeueConfiguredReusableCell : 재사용 가능한 cell 객체를 dequeue한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;!! 중요 !!&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;cell registration을 UICollectionViewDiffableDataSource.CellProvider의 클로저 내에서 만들면 안된다! 내부에서 만들 경우 cell이 reuse가 되지 않고, iOS 15 이상에는 예외가 발생할 수 있다고 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt;도 section과 사용할 데이터 타입을 받는다. itemIdentifier는 cell에 대한 item의 identifier를 의미한다. 해당 클로저는 nil이 아닌 구성된 cell 객체를 반환하며, cell provider는 collectionView에게 유효한 cell 객체를 반환해야만 한다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. snapshot을 dataSource에 apply&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var snapshot = NSDiffableDataSourceSnapshot&amp;lt;Section, Animal&amp;gt;()
snapshot.appendSections([.main])
snapshot.appendItems(animalData)
dataSource.apply(snapshot)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;NSDiffableDataSourceSnapshot&lt;/code&gt; : 특정 시점에서 view 내의 데이터의 state를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;diffableDataSources는 snapshot을 사용해서 collectionview나 tableview에 데이터를 제공한다. snapshot을 통해, 뷰에 보여질 데이터의 초기 상태를 정하고 추후에 데이터를 업데이트 해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snapshot의 데이터는 보여주고 싶은 순서로 section과 items로 구성된다. add/delete 혹은 움직여서 section/items를 보여줄 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;!! 중요 !!&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;각 section들과 item들은 Hashable 프로토콜을 채택하는 unique한 identifiers를 가지고 있어야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snapshot을 사용하여 데이터를 보여주기 위한 순서는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. snapshot을 만들고 표시하려는 데이터 state로 채운다.&lt;br /&gt;2. UI의 변경사항을 반영하도록 snapshot을 적용한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snapshot은 아래 방법으로 만들고 구성할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈 snapshot을 만들고 section들과 item들을 append한다&lt;/li&gt;
&lt;li&gt;diffableDataSource의 &lt;code&gt;snapshot()&lt;/code&gt; 메서드를 호출하여 현재 snapshot을 가져온다. 그런 다음 표시할 데이터의 새 state를 반영하도록 snapshot을 수정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-1. 모델에 Hashable 프로토콜 채택 및 identifier 프로퍼티 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;struct Animal: Codable, Hashable {
    let name, latinName, animalType, activeTime: String
    let lengthMin, lengthMax, weightMin, weightMax: String
    let lifespan, habitat, diet, geoRange: String
    let imageLink: String
    let id: Int

    let identifier = UUID()

    enum CodingKeys: String, CodingKey {
        case name
        case latinName = &quot;latin_name&quot;
        case animalType = &quot;animal_type&quot;
        case activeTime = &quot;active_time&quot;
        case lengthMin = &quot;length_min&quot;
        case lengthMax = &quot;length_max&quot;
        case weightMin = &quot;weight_min&quot;
        case weightMax = &quot;weight_max&quot;
        case lifespan, habitat, diet
        case geoRange = &quot;geo_range&quot;
        case imageLink = &quot;image_link&quot;
        case id
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 Hashable 프로토콜을 채택하는 유일한 identifier를 가져야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/hg3XEw7.png&quot; alt=&quot;&quot; width=&quot;480&quot; height=&quot;1039&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;이렇게 collection view를 사용하여 list 형태로 나타내봤다. 만약 뷰가 grid 형태로도 보여져야 한다면 tableview 1개, collection view 1개가 아니라 collection view만을 사용하여 조금 더 편하게 구현해 볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끄읕&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;Animal 코드 더보기&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;pre id=&quot;code_1641987500156&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Animal: Codable, Hashable {
    let name, latinName, animalType, activeTime: String
    let lengthMin, lengthMax, weightMin, weightMax: String
    let lifespan, habitat, diet, geoRange: String
    let imageLink: String
    let id: Int

    let identifier = UUID()
    
    enum CodingKeys: String, CodingKey {
        case name
        case latinName = &quot;latin_name&quot;
        case animalType = &quot;animal_type&quot;
        case activeTime = &quot;active_time&quot;
        case lengthMin = &quot;length_min&quot;
        case lengthMax = &quot;length_max&quot;
        case weightMin = &quot;weight_min&quot;
        case weightMax = &quot;weight_max&quot;
        case lifespan, habitat, diet
        case geoRange = &quot;geo_range&quot;
        case imageLink = &quot;image_link&quot;
        case id
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;AnimalListCell 코드 더보기&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;pre id=&quot;code_1641987461039&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

private extension UIConfigurationStateCustomKey {
    static let animal = UIConfigurationStateCustomKey(&quot;animal&quot;)
}

extension UIConfigurationState {
    var animalData: Animal? {
        get { return self[.animal] as? Animal }
        set { self[.animal] = newValue }
    }
}

class AnimalListCell: UICollectionViewListCell {
    private var animalData: Animal?
    private func defaultAnimalConfiguration() -&amp;gt; UIListContentConfiguration {
        return .subtitleCell()
    }
    
    private let animalTypeLabel = UILabel()
    private var animalTypeConstraints: (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)?
    
    private lazy var animalListContentView = UIListContentView(configuration: defaultAnimalConfiguration())
    
    func update(with newAnimalData: Animal) {
        guard animalData != newAnimalData else {
            return
        }
        animalData = newAnimalData
        setNeedsUpdateConfiguration()
    }
    
    override var configurationState: UICellConfigurationState {
        var state = super.configurationState
        state.animalData = self.animalData
        return state
    }
}

extension AnimalListCell {
    func setupViewsIfNeeded() {
        guard animalTypeConstraints == nil else {
            return
        }
        
        [animalListContentView, animalTypeLabel].forEach {
            contentView.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        let constraints = (leading:
                            animalTypeLabel.leadingAnchor.constraint(greaterThanOrEqualTo: animalListContentView.trailingAnchor),
                           trailing:
                            animalTypeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) )
        
        NSLayoutConstraint.activate([
            animalListContentView.topAnchor.constraint(equalTo: contentView.topAnchor),
            animalListContentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            animalListContentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            animalTypeLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            constraints.leading,
            constraints.trailing
        ])
        
        animalTypeConstraints = constraints
    }
    
    override func updateConfiguration(using state: UICellConfigurationState) {
        setupViewsIfNeeded()
        
        var content = defaultAnimalConfiguration().updated(for: state)
        
        content.image = urlToImage(state.animalData?.imageLink ?? &quot;&quot;)
        content.imageProperties.maximumSize = CGSize(width: 50, height: 50)
        content.text = state.animalData?.name
        content.textProperties.font = .preferredFont(forTextStyle: .headline)
        content.secondaryText = &quot;평균 수명: \(state.animalData?.lifespan ?? &quot;&quot;)년&quot;
        
        animalListContentView.configuration = content
        
        animalTypeLabel.text = state.animalData?.animalType ?? &quot;1&quot;
        
    }
}

extension AnimalListCell {
    func urlToImage(_ urlString: String) -&amp;gt; UIImage? {
        guard let url = URL(string: urlString),
              let data = try? Data(contentsOf: url),
              let image = UIImage(data: data) else {
                  return nil
              }
        
        return image
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;ViewController 코드 더보기&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;pre id=&quot;code_1641987420939&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

enum Section {
    case main
}

class ViewController: UIViewController {
    var animalData: [Animal] = []
    let apiHandler = APIService()
    
    var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;!
    
    override func viewDidLoad() {
        navigationItem.title = &quot;ZOO&quot;
        view.backgroundColor = .white
        apiHandler.getAnimalData { result in
            switch result {
            case .success(let animals):
                self.animalData = animals
                DispatchQueue.main.async {
                    self.configureCollectionView()
                    self.configureDataSource()
                }                
            case .failure(let error):
                print(error)
            }
            
        }
    }
}

extension ViewController {
    func createListLayout() -&amp;gt; UICollectionViewCompositionalLayout {
        let config = UICollectionLayoutListConfiguration(appearance: .plain)
        return UICollectionViewCompositionalLayout.list(using: config)
    }
    
    func configureCollectionView() {
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createListLayout())
        view.addSubview(collectionView)
    }
    
    func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration&amp;lt;AnimalListCell, Animal&amp;gt; { (cell, indexPath, animal) in
            cell.update(with: animal)
            cell.accessories = [.disclosureIndicator()]
        }
        
        dataSource = UICollectionViewDiffableDataSource&amp;lt;Section, Animal&amp;gt;(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) -&amp;gt; UICollectionViewCell? in
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        
        var snapshot = NSDiffableDataSourceSnapshot&amp;lt;Section, Animal&amp;gt;()
        snapshot.appendSections([.main])
        snapshot.appendItems(animalData)
        dataSource.apply(snapshot)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views&quot;&gt;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views&lt;/a&gt;&lt;/p&gt;</description>
      <category> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/555</guid>
      <comments>https://leechamin.tistory.com/555#entry555comment</comments>
      <pubDate>Wed, 12 Jan 2022 20:47:51 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] URLSession Completion Handler와 Delegate</title>
      <link>https://leechamin.tistory.com/554</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;URLSession Completion Handler와 Delegate 를 활용하여 데이터 받아오는 방법&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;URL Session을 통해 data task를 생성하고 메모리에 직접적으로 데이터를 받아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버와 자그마한 상호작용을 위해서는 &lt;code&gt;URLSessionDataTask&lt;/code&gt; 클래스를 사용하여 메모리에 response data를 받을 수 있다. 만약 데이터를 파일 시스템에 다운로드 받아야 한다면, &lt;code&gt;URLSessionDownloadTask&lt;/code&gt;를 사용하면 된다. data task는 웹 서비스를 호출할 때 이상적인 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;task를 생성하기 위해 URL session 인스턴스를 사용하게 된다. 만약 간단한 경우라면 &lt;code&gt;URLSession&lt;/code&gt; 클래스의 &lt;code&gt;shared&lt;/code&gt; 인스턴스를 사용할 수 있다. 만약 delegate의 콜백을 통해 상호작용 해야한다면, &lt;code&gt;shared&lt;/code&gt; 인스턴스를 쓰기 보다는 session을 생성할 필요가 있다. 세션을 생성할 때는 &lt;code&gt;URLSessionConfiguration&lt;/code&gt; 인스턴스를 사용하고, 또한 &lt;code&gt;URLSessionDelegate&lt;/code&gt; 또는 하위 프로토콜 중 하나를 구현하는 클래스도 전달한다. (이 부분은 나중에 코드와 함께 보면 이해가 갈 것이다!) 세션은 여러 tasks를 생성하기 위해 재사용 될 수 있으며 이에 각각에 해당하는 유일한 configuration이 필요하고, 세션을 생성하고 프로퍼티로 저장해서 사용해야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;세션을 보유하고 있다면 &lt;code&gt;dataTask()&lt;/code&gt; 메서드를 통해 data task를 생성할 수 있다. task는 suspended 상태로 생성되기 때문에 &lt;code&gt;resume()&lt;/code&gt;을 호출하여 시작시켜줘야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Completion Handler를 활용하여 결과 받기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 받기 위한 가장 간단한 방법은 data task를 생성하고 completion handler에서 사용하는 것이다. 이에 task는 서버의 response, data, 가능한 error를 completion handler 블록에 전달해준다. 아래의 그림을 보면 세션과 task간의 관계, 어떻게 completion handler에 결과가 전달되는지 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://docs-assets.developer.apple.com/published/c7124fb5d7/bf4501ff-82b2-4dd4-9ec3-243ef0e70d21.png&quot; alt=&quot;&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;completion handler를 사용하여 data task를 생성할 수 있다(&lt;code&gt;dataTask()&lt;/code&gt; 호출) 이제 completion handler는 3가지 일을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;error&lt;/code&gt; 파라미터가 &lt;code&gt;nil&lt;/code&gt;인지 확인한다. 만약 &lt;code&gt;nil&lt;/code&gt;이 아니라면 전송 에러가 발생한 것으로 에러를 처리하고 종료시켜준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response&lt;/code&gt; 파라미터를 확인하여 성공을 나타내는 상태 코드를 확인하고 MIME 타입이 원하는 값인지 확인해야한다. 만약 아니라면 서버 에러를 처리하고 종료한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data&lt;/code&gt; 인스턴스를 필요에 따라 사용하면 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 문서의 예제가 제일 좋지만 직접 실험해보고 적용해본 코드로 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 살펴보자면 &lt;a href=&quot;https://zoo-animal-api.herokuapp.com/&quot;&gt;Zoo Animal API&lt;/a&gt;를 활용하여 랜덤한 동물을 화면에 보여주도록 한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 모델을 만들어준다.&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;struct Animal: Codable {
    let name, latinName, animalType, activeTime: String
    let lengthMin, lengthMax, weightMin, weightMax: String
    let lifespan, habitat, diet, geoRange: String
    let imageLink: String
    let id: Int

    enum CodingKeys: String, CodingKey {
        case name
        case latinName = &quot;latin_name&quot;
        case animalType = &quot;animal_type&quot;
        case activeTime = &quot;active_time&quot;
        case lengthMin = &quot;length_min&quot;
        case lengthMax = &quot;length_max&quot;
        case weightMin = &quot;weight_min&quot;
        case weightMax = &quot;weight_max&quot;
        case lifespan, habitat, diet
        case geoRange = &quot;geo_range&quot;
        case imageLink = &quot;image_link&quot;
        case id
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 만드는 부분까지 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰는 만들었다고 가정하고, 핵심이 되는 네트워킹 부분을 살펴보자. 우선 첫 번째로 볼 방법은 completion handler를 활용하는 것이니 이에 포커스를 맞춰보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;url, request 생성&lt;/li&gt;
&lt;li&gt;shared 인스턴스에 dataTask() 호출&lt;/li&gt;
&lt;li&gt;completion handler에 넘어오는 data, response, error 처리&lt;/li&gt;
&lt;li&gt;순서대로 error -&amp;gt; response -&amp;gt; data 검증&lt;/li&gt;
&lt;li&gt;데이터 디코딩 성공시 completion handler 실행!&lt;/li&gt;
&lt;li&gt;생성한 task resume 시키기&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class APIService {
    func getAnimalData(completion: @escaping (Animal) -&amp;gt; ()) {
        guard let url = URL(string: &quot;https://zoo-animal-api.herokuapp.com/animals/rand&quot;) else {
            return
        }
        let request = URLRequest(url: url)


        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // error 검증
            if let error = error {
                print(error.localizedDescription)
                return
            }

            // response status code 검증
            guard let httpResponse = response as? HTTPURLResponse,
                  (200..&amp;lt;300).contains(httpResponse.statusCode) else {
                      print(&quot;error&quot;)
                      return
                  }

            // data 검증 및 사용
            if let data = data {
                do {
                    let receivedData = try JSONDecoder().decode(Animal.self, from: data)
                    completion(receivedData)
                } catch {
                    print(error.localizedDescription)
                }
            }

        }

        task.resume()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url로 넘어오는 이미지도 &lt;code&gt;UIImage&lt;/code&gt;로 변환해주는 코드를 추가해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class ImageLoader {
    static func loadImage(from url: String, completion: @escaping (UIImage?) -&amp;gt; ()) {
        // 입력된 url 체크
        if url.isEmpty {
            completion(nil)
            return
        }

        // URL 타입으로 변경
        guard let imageURL = URL(string: url) else {
            return
        }


        DispatchQueue.global(qos: .background).async {
            // URL -&amp;gt; Data
            if let data = try? Data(contentsOf: imageURL) {
                // Data -&amp;gt; UIImage
                let image = UIImage(data: data)!

                // UI 업데이트를 위한 main 쓰레드 사용
                DispatchQueue.main.async {
                    completion(image)
                }
            } else {
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 보자면 여기도 마찬가지로 &lt;code&gt;@escaping&lt;/code&gt;를 활용하여 문자열을 &lt;code&gt;UIImage&lt;/code&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;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;url 문자열 검사&lt;/li&gt;
&lt;li&gt;string -&amp;gt; URL&lt;/li&gt;
&lt;li&gt;URL -&amp;gt; Data&lt;/li&gt;
&lt;li&gt;(성공시) Data -&amp;gt; UIImage &amp;amp; UI업데이트&lt;/li&gt;
&lt;li&gt;(실패시) nil&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 모든 준비가 되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 컨트롤러에서 세팅을 마치고 버튼의 액션으로 서버에 요청해서 UI 세팅을 하는 기능을 추가해주자.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@objc func setupData() {
    let group = DispatchGroup()

    apiHandler.getAnimalData { animal in
        DispatchQueue.main.async(group: group) {
            ImageLoader.loadImage(from: animal.imageLink) { animalImage in
                self.imageView.image = animalImage
            }
        }

        group.notify(queue: DispatchQueue.main) {
            self.nameLabel.text = &quot;동물 이름 : \(animal.name)&quot;
            self.lifeLabel.text = &quot;평균 수명 : \(animal.lifespan)년&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에는 간단하게 사진과 동물의 이름, 평균 수명 그리고 다음 랜덤한 동물을 나타내기 위한 버튼만을 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행해보면 다음과 같은 결과를 얻을 수 있다. (아직 이미지 / 레이블의 동기화 순서를 잡아주지는 않았다..!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/7JBml9Y.gif&quot; alt=&quot;&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;중요한 점은 completion handler가 task가 생성된 곳이 아니라 GCD(메인큐)를 활용하였다는 것이다. 그러므로 data나 error에 대해서 UI를 업데이트 해줄 수 있다(위 예제에서는 error에는 GCD 적용X)&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;Delegate를 활용하여 세부 정보와 결과를 전송받기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data task를 생성할 때에, task의 활동에 더 높은 수준의 접근을 위해 데이터 작업을 작성할 때 completion handler를 제공하는 것 대신에 세션에 delegate를 설정할 수 있다. 아래 그림에서 이를 나타내주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://docs-assets.developer.apple.com/published/8b22355c7f/730c8e1b-654f-4eb9-9c63-d439a69ac5d2.png&quot; alt=&quot;&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;code&gt;URLSessionDataDelegate&lt;/code&gt;의 &lt;code&gt;urlSession(_:dataTask:didReceive:)&lt;/code&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;우선 delegate 방식으로 접근하기 위해서는 간단한 &lt;code&gt;shared&lt;/code&gt; 인스턴스가 아닌, &lt;code&gt;URLSession&lt;/code&gt; 인스턴스를 생성해야한다. 새로운 세션을 생성하는 것은 클래스가 세션의 delgate 역할을 가능하게 하며 이는 위 그림을 통해 나타나있다.&lt;/p&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;code&gt;URLSessionDelegate&lt;/code&gt;,&lt;code&gt;URLSessionTaskDelegate&lt;/code&gt;,&lt;code&gt;URLSessionDataDelegate&lt;/code&gt;,&lt;code&gt;URLSessionDownloadDelegate&lt;/code&gt;)을 정의해준다. 그리고 &lt;code&gt;init(configuration:delegate:delegateQueue:)&lt;/code&gt;를 활용하여 URL session의 인스턴스를 생성한다. configuration 인스턴스는 이 이니셜라이저를 통해 커스터마이징 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서, &lt;code&gt;waitsForConnectivity&lt;/code&gt;를 &lt;code&gt;true&lt;/code&gt;로 두는 좋은 생각을 해볼 수 있다. 그렇게 되면 세션은 요구되는 연결이 불가능할 때 바로 실패하기 보다는, 적합한 연결을 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이제 가장 처음해야하는 &lt;code&gt;URLSession&lt;/code&gt; 생성을 먼저해주자.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// VC
private lazy var session: URLSession = {
    let config = URLSessionConfiguration.default
    config.waitsForConnectivity = true
    return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 앞으로 볼 코드에서 data task를 시작시키고, 받은 data와 error를 처리하기 위해 delegate 콜백을 활용하는 방법을 볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 메서드의 역할부터 보고 넘어가자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;urlSession(_:dataTask:didReceive:completionHandler:)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;response가 성공적인 HTTP 상태 코드를 가지고 있는지 검증하고 MIME 타입이 text/html인지 text/plain인지 확인한다. 만약 두 경우 모두 아니라면 task는 취소되며, 정상인 경우는 진행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;urlSession(_:dataTask:didReceive:)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;task를 통해 받은 각 &lt;code&gt;Data&lt;/code&gt; 인스턴스를 &lt;code&gt;receivedData&lt;/code&gt;라고 불리는 버퍼에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;urlSession(_:task:didCompleteWithError:)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 전송 오류가 발생했는지 확인한다.&lt;/li&gt;
&lt;li&gt;오류가 없다면 &lt;code&gt;receivedData&lt;/code&gt; 버퍼를 원하는 모델 타입에 맞게 디코딩해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// VC
func startLoad() {
        let url = URL(string: &quot;https://zoo-animal-api.herokuapp.com/animals/rand&quot;)!
        receivedData = Data()
        let task = session.dataTask(with: url)
        task.resume()
    }

func setupDataByDelegate(from data: Animal) {        
    ImageLoader.loadImage(from: data.imageLink) { image in
        self.imageView.image = image
    }

    DispatchQueue.main.async {
        self.nameLabel.text = &quot;동물 이름 : \(data.name)&quot;
        self.lifeLabel.text = &quot;평균 수명 : \(data.lifespan)년&quot;
    }
}

extension ViewController: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -&amp;gt; Void) {
        guard let response = response as? HTTPURLResponse,
              (200..&amp;lt;300).contains(response.statusCode) else {
                  completionHandler(.cancel)
                  return
              }

        // allow the load operation to continue
        completionHandler(.allow)
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        self.receivedData?.append(data)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error  {
            print(error.localizedDescription)
        } else if let receivedData = self.receivedData {
            do {
                let data = try JSONDecoder().decode(Animal.self, from: receivedData)
                setupDataByDelegate(from: data)
            } catch {
                print(error.localizedDescription)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/ObnAkDX.gif&quot; alt=&quot;&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;이외에도 다양한 delegate 프로토콜은 인증 문제, 리다이렉션, 기타 특수한 경우를 처리하기 위해 위에 나타나 있는 코드 이상의 방법을 제공한다. URLSession 문서를 더 살펴보면 다양한 콜백에 대해서 알아볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;Animal.swift 더보기&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;pre id=&quot;code_1641227978939&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  Animal.swift
//  networkingTest
//
//  Created by 이차민 on 2022/01/03.
//

import Foundation

struct Animal: Codable {
    let name, latinName, animalType, activeTime: String
    let lengthMin, lengthMax, weightMin, weightMax: String
    let lifespan, habitat, diet, geoRange: String
    let imageLink: String
    let id: Int

    enum CodingKeys: String, CodingKey {
        case name
        case latinName = &quot;latin_name&quot;
        case animalType = &quot;animal_type&quot;
        case activeTime = &quot;active_time&quot;
        case lengthMin = &quot;length_min&quot;
        case lengthMax = &quot;length_max&quot;
        case weightMin = &quot;weight_min&quot;
        case weightMax = &quot;weight_max&quot;
        case lifespan, habitat, diet
        case geoRange = &quot;geo_range&quot;
        case imageLink = &quot;image_link&quot;
        case id
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;ImageLoader.swift 더보기&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;pre id=&quot;code_1641227959041&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ImageLoader.swift
//  networkingTest
//
//  Created by 이차민 on 2022/01/03.
//

import Foundation
import UIKit

class ImageLoader {
    static func loadImage(from url: String, completion: @escaping (UIImage?) -&amp;gt; ()) {
        // 입력된 url 체크
        if url.isEmpty {
            completion(nil)
            return
        }
        
        // URL 타입으로 변경
        guard let imageURL = URL(string: url) else {
            return
        }
        
        
        DispatchQueue.global(qos: .background).async {
            // URL -&amp;gt; Data
            if let data = try? Data(contentsOf: imageURL) {
                // Data -&amp;gt; UIImage
                let image = UIImage(data: data)!
                
                // UI 업데이트를 위한 main 쓰레드 사용
                DispatchQueue.main.async {
                    completion(image)
                }
            } else {
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;APIService.swift 더보기&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;pre id=&quot;code_1641227894370&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  APIService.swift
//  networkingTest
//
//  Created by 이차민 on 2022/01/03.
//

import Foundation

class APIService {
    func getAnimalData(completion: @escaping (Animal) -&amp;gt; ()) {
        guard let url = URL(string: &quot;https://zoo-animal-api.herokuapp.com/animals/rand&quot;) else {
            return
        }
        let request = URLRequest(url: url)


        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // error 검증
            if let error = error {
                print(error.localizedDescription)
                return
            }
            
            // response status code 검증
            guard let httpResponse = response as? HTTPURLResponse,
                  (200..&amp;lt;300).contains(httpResponse.statusCode) else {
                      print(&quot;error&quot;)
                      return
                  }

            // data 검증 및 사용
            if let data = data {
                do {
                    let receivedData = try JSONDecoder().decode(Animal.self, from: data)
                    completion(receivedData)
                } catch {
                    print(error.localizedDescription)
                }
            }

        }
        
        task.resume()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;ViewController.swift 더보기&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_1641227851875&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ViewController.swift
//  networkingTest
//
//  Created by 이차민 on 2022/01/03.
//

import UIKit

class ViewController: UIViewController {

    var receivedData: Data?
    var animalData: [Animal] = []
    
    let apiHandler = APIService()
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.default
        config.waitsForConnectivity = true
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    let imageView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFit
        return iv
    }()
    
    let nameLabel: UILabel = {
        let lb = UILabel()
        lb.textColor = .black
        lb.font = .preferredFont(forTextStyle: .title2)
        
        return lb
    }()
    
    let lifeLabel: UILabel = {
        let lb = UILabel()
        lb.textColor = .black
        lb.font = .preferredFont(forTextStyle: .title3)
        
        return lb
    }()
    
    let refreshButton: UIButton = {
        let bt = UIButton()
        
        bt.setTitle(&quot;다음 동물 보기&quot;, for: .normal)
        bt.setTitleColor(.white, for: .normal)
        bt.titleLabel?.font = UIFont(name: &quot;Helvetica-Bold&quot;, size: 17)
        bt.titleLabel?.textAlignment = .center
        bt.sizeToFit()
        
        let size: CGFloat = 5
        bt.titleEdgeInsets = UIEdgeInsets(top: size, left: size, bottom: size, right: size)
        bt.layer.masksToBounds = false
        bt.layer.cornerRadius = 5
        bt.backgroundColor = .systemIndigo
        bt.addTarget(self, action: #selector(setupData), for: .touchUpInside)
        
        return bt
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        startLoad()
//        setupData()
    }
    
    // default method
    @objc func setupData() {
        let group = DispatchGroup()
        
        apiHandler.getAnimalData { animal in
            DispatchQueue.main.async(group: group) {
                ImageLoader.loadImage(from: animal.imageLink) { animalImage in
                    self.imageView.image = animalImage
                }
            }
            
            group.notify(queue: DispatchQueue.main) {
                self.nameLabel.text = &quot;동물 이름 : \(animal.name)&quot;
                self.lifeLabel.text = &quot;평균 수명 : \(animal.lifespan)년&quot;
            }
        }
    }
    
    func setupUI() {
        [imageView, nameLabel, lifeLabel, refreshButton].forEach {
            self.view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
            imageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            imageView.heightAnchor.constraint(equalToConstant: self.view.frame.height / 3),
            
            nameLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20),
            nameLabel.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
            
            lifeLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 20),
            lifeLabel.centerXAnchor.constraint(equalTo: nameLabel.centerXAnchor),
            
            
            refreshButton.topAnchor.constraint(equalTo: lifeLabel.bottomAnchor, constant: 40),
            refreshButton.centerXAnchor.constraint(equalTo: lifeLabel.centerXAnchor),
            refreshButton.widthAnchor.constraint(equalToConstant: 130)
        ])
    }

    // delegate
    
    func startLoad() {
        let url = URL(string: &quot;https://zoo-animal-api.herokuapp.com/animals/rand&quot;)!
        receivedData = Data()
        let task = session.dataTask(with: url)
        task.resume()
    }
    
    func setupDataByDelegate(from data: Animal) {        
        ImageLoader.loadImage(from: data.imageLink) { image in
            self.imageView.image = image
        }
        
        DispatchQueue.main.async {
            self.nameLabel.text = &quot;동물 이름 : \(data.name)&quot;
            self.lifeLabel.text = &quot;평균 수명 : \(data.lifespan)년&quot;
        }
    }
}

extension ViewController: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -&amp;gt; Void) {
        guard let response = response as? HTTPURLResponse,
              (200..&amp;lt;300).contains(response.statusCode) else {
                  completionHandler(.cancel)
                  return
              }
        
        // allow the load operation to continue
        completionHandler(.allow)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        self.receivedData?.append(data)
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error  {
            print(error.localizedDescription)
        } else if let receivedData = self.receivedData {
            do {
                let data = try JSONDecoder().decode(Animal.self, from: receivedData)
                setupDataByDelegate(from: data)
            } catch {
                print(error.localizedDescription)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끄읕&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref: &lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1641227706394&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;Apple Developer Documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory&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;Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;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;</description>
      <category> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <category>swift URLSession</category>
      <category>URLSession</category>
      <category>URLSession completion handler</category>
      <category>URLSession Delegate</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/554</guid>
      <comments>https://leechamin.tistory.com/554#entry554comment</comments>
      <pubDate>Tue, 4 Jan 2022 01:40:24 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Dynamic Type에 따른 AutoLayout 조정하기 (feat. WWDC 2017)</title>
      <link>https://leechamin.tistory.com/553</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/245/&quot;&gt;WWDC 2017 Building Apps with Dynamic Type&lt;/a&gt;를 보다가 마침 현재 프로젝트에도 있는 문제가 있어서 적용 가능해보이는 부분을 발견했다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://a11y-guidelines.orange.com/en/mobile/images/iOSdev/wwdc17-245-SideBySideText.png&quot; alt=&quot;&quot; width=&quot;515&quot; height=&quot;381&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;code&gt;UILabel&lt;/code&gt;이 나란히 2개 있는 상황에서 기기의 폰트 크기가 커짐에 따라 어떻게 레이아웃을 잡아야하는지에 대한 내용이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 텍스트 일부가 &lt;code&gt;...&lt;/code&gt;등으로 짤리면 안되고, 뭔가 텍스트의 줄바꿈이 이상하게 되어있어도 안된다고 이야기하고 있다. 딱 봐도 이상해보이긴 한다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 권장하는 방식은 텍스트 크기가 커졌을 때, 우측 &lt;code&gt;UILabel&lt;/code&gt;을 좌측 &lt;code&gt;UILabel&lt;/code&gt; 아래로 내리는 방법이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC에서는 아래와 같은 방법으로 구현할 수 있다고 이야기하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://a11y-guidelines.orange.com/en/mobile/images/iOSdev/wwdc17-245-AutoLayoutsystemSpacingConstraints_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;우측 &lt;code&gt;UILabel&lt;/code&gt;의 firstBaseline을 좌측 &lt;code&gt;UILabel&lt;/code&gt; lastBaseline에 맞추는데, 바로 아래 딱 붙게해줄 수 있도록 &lt;code&gt;constriantEqualToSystemSpacingBelow&lt;/code&gt;를 주고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&quot;&gt;공식문서&lt;/a&gt;를 보면 아래와 같이 사용하고 있는 것 같다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;constraint(equalToSystemSpacingBelow:multiplier:)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괄호의 위치가 조금 다른 느낌...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이렇게 레이아웃을 주면 vertical하게 위치시킬 수 있다고 한다. 이제 어떻게 시스템 폰트 크기를 식별하느냐인데, 이 또한 WWDC에서 알려주고 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://a11y-guidelines.orange.com/en/mobile/images/iOSdev/wwdc17-245-PreferredContentSizeCategory_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 방법이 있는데, 하나는 &lt;code&gt;isAccessibilityCategory&lt;/code&gt;를 사용하는 방법, 다른 하나는 특정 글자 크기를 지정해서 그걸 넘어서는지 확인하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;isAccessibilityCategory&lt;/code&gt;의 문서를 보면&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/1623110-accessibilitymedium&quot;&gt;&lt;code&gt;accessibilityMedium&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/1623050-accessibilitylarge&quot;&gt;&lt;code&gt;accessibilityLarge&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/1622959-accessibilityextralarge&quot;&gt;&lt;code&gt;accessibilityExtraLarge&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/1623120-accessibilityextraextralarge&quot;&gt;&lt;code&gt;accessibilityExtraExtraLarge&lt;/code&gt;&lt;/a&gt;와 &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/1623086-accessibilityextraextraextralarg&quot;&gt;&lt;code&gt;accessibilityExtraExtraExtraLarge&lt;/code&gt;&lt;/a&gt; 만 true의 값을 반환한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우선은 이를 활용해서 레이아웃을 따로 잡아주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그러면 한 번 구현해보자!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 레이아웃을 담아줄 변수를 생성하자.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;var LargeFontTypeLayout: [NSLayoutConstraint]?
var standartFontTypeLayout: [NSLayoutConstraint]?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;isAccessibilityCategory&lt;/code&gt; 여부에 따라 나뉘는 두 가지 케이스에 맞게 레이아웃을 잡아주기 위해 두 가지 레이아웃 배열을 만들어줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 케이스에서만 특수하게 적용해줄 레이아웃들을 각 배열에 담아준다. 공통으로 적용되는 레이아웃들은 굳이 배열에 담지 않고도 바로 적용시켜줘도 된다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;LargeFontTypeLayout = [
    second.firstBaselineAnchor.constraint(equalToSystemSpacingBelow: first.lastBaselineAnchor, multiplier: 1),
    second.leadingAnchor.constraint(equalTo: first.leadingAnchor)
]

standartFontTypeLayout = [
    second.centerYAnchor.constraint(equalTo: first.centerYAnchor), 
    second.leadingAnchor.constraint(equalTo: first.trailingAnchor, constant: 10)
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 각 케이스에 따라 담아준 레이아웃 배열을 active시켜주면 된다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
    LargeFontTypeLayout?.forEach {
        $0.isActive = true
    }
} else {
    standartFontTypeLayout?.forEach {
        $0.isActive = true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 여기까지 잡아준 레이아웃 코드를 메서드로 잘 정리해서 &lt;code&gt;viewDidLoad()&lt;/code&gt;에 넣어주면 앱이 처음 실행되었을 때 기기의 폰트 크기에 따라 잘 분기가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 중간에 앱을 백그라운드로 보내고, 기기의 폰트 크기를 바꾸게 되면 현재 코드로서는 대응하지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 추가해줘야하는 코드가 있는데, 바로 기기의 폰트 설정이 바뀌었다는 정보를 받기 위해 NotificationCenter에 &lt;code&gt;addObserver&lt;/code&gt;를 해줘야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(setLayoutByDynamicType), name: UIContentSizeCategory.didChangeNotification, object: nil)
}

// setLayoutByDynamicType
@objc func setLayoutByDynamicType() {
    if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
        standartFontTypeLayout?.forEach {
            $0.isActive = false
        }
        LargeFontTypeLayout?.forEach {
            $0.isActive = true
        }    
    } else {
        LargeFontTypeLayout?.forEach {
            $0.isActive = false
        }
        standartFontTypeLayout?.forEach {
            $0.isActive = true
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템에서 폰트 카테고리가 바뀌었을 때 post를 주는데 그 채널은 &lt;code&gt;UIContentSizeCategory.didChangeNotification&lt;/code&gt;이다. 이에 기본적으로 제공해주는 채널에 대해 &lt;code&gt;addObserver&lt;/code&gt;를 해두고 알림을 받았을 때 실행할 메서드를 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 것은 없고 두 케이스의 레이아웃 배열을 스위치 온/오프 하듯이 원하지 않는 레이아웃을 deactive해주고, 원하는 레이아웃을 active해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 결과물을 확인해 볼 시간...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dynamic1.gif&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;1385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qkyd0/btroec3FZAE/fSDolKAgyy7bfzqKxje2L1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qkyd0/btroec3FZAE/fSDolKAgyy7bfzqKxje2L1/img.gif&quot; data-alt=&quot;Standard &amp;amp;gt; Larger Text&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qkyd0/btroec3FZAE/fSDolKAgyy7bfzqKxje2L1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/qkyd0/btroec3FZAE/fSDolKAgyy7bfzqKxje2L1/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;867&quot; data-filename=&quot;dynamic1.gif&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;1385&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Standard &amp;gt; Larger Text&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dynamic2.gif&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;1385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E7uTw/btrob8nQ3GK/A1dr8rc0j8qp7SGrvqKAKK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E7uTw/btrob8nQ3GK/A1dr8rc0j8qp7SGrvqKAKK/img.gif&quot; data-alt=&quot;Larger Text &amp;amp;gt; Standard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E7uTw/btrob8nQ3GK/A1dr8rc0j8qp7SGrvqKAKK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/E7uTw/btrob8nQ3GK/A1dr8rc0j8qp7SGrvqKAKK/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;867&quot; data-filename=&quot;dynamic2.gif&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;1385&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Larger Text &amp;gt; Standard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 텍스트 크기에 맞게 레이아웃이 자동으로 적용되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 하고 알게된 사실인데... 만약 저 두 개의 &lt;code&gt;UILabel&lt;/code&gt;을 &lt;code&gt;UIStackView&lt;/code&gt;로 묶어도 되는 상황이라면 더 쉽게 해결해 볼 수 있다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글자가 커지는 경우 &lt;code&gt;.axis&lt;/code&gt;만 &lt;code&gt;vertical&lt;/code&gt;로 바꿔주고, 기본인 경우 다시 &lt;code&gt;horizontal&lt;/code&gt;로 바꿔주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라 필요하고, 필요하지 않는 레이아웃을 아까 봤던 방법처럼 active/deactive해서 적용해주면 더 간단하게 해결해 볼 수도 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/245/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/videos/play/wwdc2017/245/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639667986981&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;Building Apps with Dynamic Type - WWDC17 - Videos - Apple Developer&quot; data-og-description=&quot;With Dynamic Type, people choose their preferred text size and iOS switches fonts automatically as needed. Understand why Dynamic Type is...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2017/245/&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2017/245/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkKZb8/hyMJF7TLJ8/HGgtPN5fs9lOviKg9ozcDK/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/245/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2017/245/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkKZb8/hyMJF7TLJ8/HGgtPN5fs9lOviKg9ozcDK/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Building Apps with Dynamic Type - WWDC17 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;With Dynamic Type, people choose their preferred text size and iOS switches fonts automatically as needed. Understand why Dynamic Type is...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639667992199&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;Apple Developer Documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&quot; data-og-url=&quot;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/uikit/nslayoutyaxisanchor/2866022-constraint&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;Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/2897444-isaccessibilitycategory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/uicontentsizecategory/2897444-isaccessibilitycategory&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639668015550&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;Apple Developer Documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/2897444-isaccessibilitycategory&quot; data-og-url=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/2897444-isaccessibilitycategory&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/2897444-isaccessibilitycategory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/2897444-isaccessibilitycategory&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;Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://a11y-guidelines.orange.com/en/mobile/ios/wwdc/2017/245/&quot;&gt;https://a11y-guidelines.orange.com/en/mobile/ios/wwdc/2017/245/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639668028090&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;WWDC 2017 : Building Apps with Dynamic Type - Orange digital accessibility guidelines&quot; data-og-description=&quot;WWDC 2017 : Building Apps with Dynamic Type This video available on the official Apple website (session 245) aims at defining what Dynamic Type is, based on good practice implementation. Various contents and their video timelapse are indicated hereunder: T&quot; data-og-host=&quot;a11y-guidelines.orange.com&quot; data-og-source-url=&quot;https://a11y-guidelines.orange.com/en/mobile/ios/wwdc/2017/245/&quot; data-og-url=&quot;https://a11y-guidelines.orange.com/en/mobile/ios/wwdc/2017/245/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://a11y-guidelines.orange.com/en/mobile/ios/wwdc/2017/245/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://a11y-guidelines.orange.com/en/mobile/ios/wwdc/2017/245/&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;WWDC 2017 : Building Apps with Dynamic Type - Orange digital accessibility guidelines&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;WWDC 2017 : Building Apps with Dynamic Type This video available on the official Apple website (session 245) aims at defining what Dynamic Type is, based on good practice implementation. Various contents and their video timelapse are indicated hereunder: T&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;a11y-guidelines.orange.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> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/553</guid>
      <comments>https://leechamin.tistory.com/553#entry553comment</comments>
      <pubDate>Fri, 17 Dec 2021 00:19:03 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] Factory Pattern과 Abstract Factory Pattern</title>
      <link>https://leechamin.tistory.com/552</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Factory Pattern&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Factory pattern이라는 말 그 자체로 객체를 생성해주는 역할을 한다. Factory pattern의 정의는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;즉 객체를 통해 인스턴스를 만드는 것이 아니라 Factory method를 통해 인스턴스를 생성하고 사용하게 되는 것이다. 어떤 과정으로 인스턴스가 생성되는지 그 과정에서 알아야 하는 세부 사항들을 줄여주는 효과가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 storyboard를 기반으로 View Controller의 인스턴스를 만들려고 할 때 알아야 하는 값이 두 가지 있다. 바로 storyboard의 name과 identifier이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 View Controller를 생성할 때 마다 storyboard 각각의 name과 identifier를 기억하고 있기는 힘들다. 이에 이런 부분을 factory method에 담아 클라이언트는 인스턴스가 어떻게 생기게 되는지 모르게 되고 그저 반환되는 인스턴스를 사용하게 된다. 즉 클라이언트 입장에서는 storyboard의 name과 identifier를 모르고 factory만 알면, 원하는 View Controller의 인스턴스를 생성하여 사용할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 동일한 공정을 이용해서 서로 다른 인스턴스를 만드는 것과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Factory Pattern&lt;/code&gt;의 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성이 높다.&lt;/li&gt;
&lt;li&gt;의존성/결합도가 낮아진다.&lt;/li&gt;
&lt;li&gt;인스턴스의 사용과 생성을 분리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 어떤 이유에서 factory pattern을 사용해야하는지 예제를 보면서 살펴보자.&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;code&gt;Rideable&lt;/code&gt; 프로토콜을 정의해준다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol Rideable {
    var brandName: String { get }

    func ride()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 이 프로토콜을 채택하는 차, 자전거 타입을 생성해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;struct Car: Rideable {
    var brandName: String

    func ride() {
        print(&quot;\(self.brandName)타고 가자!&quot;)
    }
}

struct Bicycle: Rideable {
    var brandName: String

    func ride() {
        print(&quot;\(self.brandName)타고 가자!&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 이렇게까지 만들고 필요한 부분에서 &lt;code&gt;Car&lt;/code&gt;나 &lt;code&gt;Bicycle&lt;/code&gt;을 초기화하여 인스턴스를 생성해서 사용했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 어떤 특정 View Controller에서 인스턴스를 생성해서 사용했다고 가정해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let bmw = Car(brandName: &quot;BMW&quot;)
        bmw.ride() // BMW타고 가자!
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그런데 만약에 &lt;code&gt;Car&lt;/code&gt;에 새로운 프로퍼티가 추가되어서 이니셜라이저가 변경되는 경우를 생각해보자. 만약 &lt;code&gt;price&lt;/code&gt;라는 프로퍼티가 추가되었다고 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 타입을 수정하게 되고, 이에 따라 불가피하게 해당 타입의 인스턴스를 생성했던 부분의 수정 또한 필요하게 된다. 지금은 한 군데에만 사용을 했지만.... 만약 10개의 View Controller에서 &lt;code&gt;Car&lt;/code&gt; 타입의 인스턴스를 생성했다면 일일이 찾아가서 초기화 부분을 모두 다 수정해줘야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분에 있어서 효율적으로 사용할 수 있는 것이 바로 &lt;b&gt;Factory pattern&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;위에서 말한 것 처럼 직접 인스턴스를 생성하는 것이 아니라 Factory를 통해 인스턴스를 생성해주는 것이다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum VehicleType {
    case car
    case bicycle
}

class VehicleFactory {
    func creatVehicle(brandName: String, vehicleType: VehicleType) -&amp;gt; Rideable {
        switch vehicleType {
        case .car:
            return Car(brandName: brandName)
        case .bicycle:
            return Bicycle(brandName: brandName)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Factory 타입을 만들어줄 수 있다. 이제 실제로 사용되는 부분에서는 인스턴스화 되는 과정을 세세하게 알지 못해도 인스턴스를 생성하고 사용할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까의 View Controller의 코드가 아래처럼 바뀔 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let vehicleFactory = VehicleFactory()
        let bmw = vehicleFactory.creatVehicle(brandName: &quot;BMW&quot;, vehicleType: .car)
        bmw.ride() // BMW타고 가자!
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 &lt;code&gt;Car&lt;/code&gt;의 이니셜라이저가 아무리 바뀌어도 ViewController에서는 신경써주지 않아도 된다. 딱 한 군데, Factory method 부분만 수정해주면 된다. 이러한 부분에서 확장성과 재사용성이 높다고 말할 수 있을 것 같다. 또한 직접적으로 &lt;code&gt;Car&lt;/code&gt;에 접근하는 것이 아니라 Factory를 거치기 때문에 의존성/결합도 또한 낮아지는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로. 이제 직접 각 객체를 통해 인스턴스를 만들지 않고 Factory를 통해 인스턴스를 만들 수 있다는 것이 큰 장점이다.&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;Abstract Factory Pattern&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &quot;추상 팩토리 패턴&quot;이라고 많이 부르는 것 같다. Abstract Factory Pattern은 앞서 봤던 Factory Pattern과 유사한데, 차이점이라고 하면 Abstract Factory Pattern은 Factory를 만드는 상위 클래스가 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 쉽게 말하면 Factory를 만드는 더 상위의 Factory가 있는 셈이다.&lt;/p&gt;
&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;code&gt;BMW&lt;/code&gt;와 &lt;code&gt;Benz&lt;/code&gt;가 있다고 해보자. 이 둘은 모두 이동수단을 만드는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 이제부터 차 말고도 자전거도 같이 만들어야 한다고 하는 경우라고 가정해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol VehicleFactory {
    func createCar() -&amp;gt; Car
    func createBicycle() -&amp;gt; Bicycle
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 활용하여 각 제조사별 Factory를 생성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class BMWFactory: VehicleFactory {
    func createCar() -&amp;gt; Car {
        return Car(brandName: &quot;BMW&quot;)
    }

    func createBicycle() -&amp;gt; Bicycle {
        return Bicycle(brandName: &quot;BMW&quot;)
    }
}

class MercedesBenzFactory: VehicleFactory {
    func createCar() -&amp;gt; Car {
        return Car(brandName: &quot;Mercedes-Benz&quot;)
    }

    func createBicycle() -&amp;gt; Bicycle {
        return Bicycle(brandName: &quot;Mercedes-Benz&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이렇게 각 Factory를 만든 것 까지 &lt;code&gt;Factory Pattern&lt;/code&gt;이라고 볼 수 있다. 하지만 지금은 N개의 Factory가 만들어질 수 있는 상황이고 이를 묶어주는 상위의 Factory를 보기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이를 가지고 무난하게 사용해볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let bmwFactory = BMWFactory()
let bmwCar = bmwFactory.createCar()
let bmwBicycle = bmwFactory.createBicycle()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Mercedes-Benz&lt;/code&gt;도 마찬가지로 동일하게 사용해줄 수 있는데, 이제 제조사 또한 신경을 크게 쓰지 않아도 되도록 상위 Factory인 &lt;code&gt;ManufacturerFactory&lt;/code&gt;를 만들어 볼 것이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class ManufacturerFactory {
    static func createFactory(manufacturer: VehicleManufacturer) -&amp;gt; VehicleFactory {
        switch manufacturer {
        case .bmw:
            return BMWFactory()
        case .mercedesBenz:
            return MercedesBenzFactory()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 상위 Factory를 가지고 Factory 객체를 만들어 줄 수 있다. 이제 그 다음부터는 Factory pattern 때 사용하던 방식과 동일하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Abstract Factory Pattern&lt;/code&gt;의 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성이 높다.&lt;/li&gt;
&lt;li&gt;객체간의 의존성/결합도가 낮아진다.&lt;/li&gt;
&lt;li&gt;SOLID의 SRP, OCP를 따른다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자 코드를 분리하여 코드의 역할이 분배되었다&lt;/li&gt;
&lt;li&gt;새로운 타입 추가에 자유롭다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 깊어지면 찾기가 어렵다..!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Factory Pattern에 대해 알아보았는데, 이를 실제 View Controller를 생성한다거나, 특정 객체를 생성할 때 적용해서 장점을 취하면 좋을 것 같다.&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;&lt;b&gt;-Ref-&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;a title=&quot;View Controller Factory in Swift&quot; href=&quot;https://medium.com/@sreeks/view-controller-factory-in-swift-1d4ee8f9b54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/@sreeks/view-controller-factory-in-swift-1d4ee8f9b54&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stevenpcurtis.medium.com/the-factory-pattern-using-swift-b534ae9f983f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stevenpcurtis.medium.com/the-factory-pattern-using-swift-b534ae9f983f&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ellyheetov/Factory-Pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ellyheetov/Factory-Pattern&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jeonyeohun.tistory.com/211&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jeonyeohun.tistory.com/211&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icksw.tistory.com/237&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://icksw.tistory.com/237&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icksw.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://icksw.tistory.com/235&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%8C%A9%ED%86%A0%EB%A6%AC_%EB%A9%94%EC%84%9C%EB%93%9C_%ED%8C%A8%ED%84%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.wikipedia.org/wiki/%ED%8C%A9%ED%86%A0%EB%A6%AC_%EB%A9%94%EC%84%9C%EB%93%9C_%ED%8C%A8%ED%84%B4&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> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/552</guid>
      <comments>https://leechamin.tistory.com/552#entry552comment</comments>
      <pubDate>Tue, 14 Dec 2021 01:12:19 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Delegation 재정복하기 (feat. 야곰 아카데미)</title>
      <link>https://leechamin.tistory.com/550</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;본 게시글은 야곰 아카데미의 Coda가 제공한 실험실 콘텐츠를 학습하며 얻은 지식과 경험을 정리한 글 입니다.&lt;br /&gt;Thanks for Coda!! &amp;zwj;♂️ &lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 블로그에 글을 남겨봅니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아카데미에서 공부하느라... 블로그가 뒷전이 되었네요... 블로그에만 올리지 않고 github에는 TIL로 매일 정리하고 있습니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각설하고 Delegation에 대해서 알아봅시다!&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Delegation이란?&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;size16&quot;&gt;Delegation이란 무엇일까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 익숙한듯 익숙하지 않은 단어.... 여기저기서 delegation pattern, delegate pattern이라는 말을 들어봤을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 항상 단어의 뜻에 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어의 뜻을 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGVQMn/btrkK7FauKh/8OmevXEt4MSnhwe24wsubK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGVQMn/btrkK7FauKh/8OmevXEt4MSnhwe24wsubK/img.png&quot; data-alt=&quot;delegation의 정의 [from 네이버 백과사전]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGVQMn/btrkK7FauKh/8OmevXEt4MSnhwe24wsubK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGVQMn%2FbtrkK7FauKh%2F8OmevXEt4MSnhwe24wsubK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;280&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;delegation의 정의 [from 네이버 백과사전]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;위임&quot;&lt;/b&gt;&lt;/span&gt;이라는 뜻이 현재 문맥에서는 적합한 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;무엇&lt;/b&gt;&lt;/span&gt;을 위임하는 것인지, 위임이라는 행동에 연관되어있는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;객체&lt;/b&gt;&lt;/span&gt;는 무엇이 있을지 생각해볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근본적으로 위임이란, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;내가 해야할 일을 다른 누군가에게 대신 시킨다&lt;/b&gt;&lt;/span&gt;는 의미를 가지고 있다. 이 맥락을 그대로 프로그래밍 세계에 가져와보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 delegation을 설명할 때 상하관계의 객체들을 예시로 한다. &quot;회장님-비서&quot;, &quot;점장님-알바&quot; 등 딱 봐도 수평적인 관계가 아님을 알 수 있다. 이는 모두 이해를 위해 현실세계에서 &quot;시킬 수 있는 누군가&quot;, &quot;시킴을 당하는 누군가&quot;를 가져온 것 뿐이지 무조건적으로 상하관계이어야 하는 것은 아니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 수평적인 관계여도 서로 어떠한 행위를 위임할 수 있는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구끼리 부탁을 한다거나, 직장 동료에게 부탁을 한다거나 수평적인 관계에서도 위임은 항상 우리의 주변에서 쉽게 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 일회성이 많지만...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 수직/수평 관계가 아니라 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;A가 시키고자 하는 일을 B가 대신 해준다&quot;&lt;/b&gt;&lt;/span&gt; 라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 왜 delegation을 할까?&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;이제 delegation이 어떤 목적을 위해 있는 것인지는 얼추 이해가 되었을 것이다. 그렇다면 왜 delegation을 하는걸까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 여러 이유가 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 해야 할 일이 너무 많아서, 몇 가지 일은 다른 누군가에게 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;책임&lt;/b&gt;&lt;/span&gt;을 넘기고 싶은 경우&amp;nbsp;&lt;/li&gt;
&lt;li&gt;내가 하기 귀찮아서, 다른 누군가 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;대신&lt;/b&gt;&lt;/span&gt; 해줬으면 해서&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등등 여러가지 이유로 현실세계에서, 프로그래밍 세계에서 위임이 일어나고 있을 것 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심이라고 생각되는 부분은 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;책임&quot;&lt;/b&gt;&lt;/span&gt;인 것 같다. 어떠한 일을 위임함으로써 책임을 일부 넘겨준다고 볼 수 있을 것 같다. 책임을 일부 넘겨줌으로써 서로의 역할과 책임이 명확해질 수 있다. 객체 지향에 대해서 조금이나마 공부해봤다면 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;역할과 책임&quot;&lt;/b&gt;&lt;/span&gt;의 중요성은 말하지 않아도 알 것이다. 그래서 개인적인 생각으로는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;객체들의 역할과 책임을 나눔으로써 객체들은 서로 요청/응답만 하는 과정을 거치며, 결국에는 의존성을 낮출 수 있다&quot;&lt;/b&gt;&lt;/span&gt;는게 delegation의 핵심 역할인 것 같다.&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;&quot;선생님&quot;&lt;/b&gt;이 &lt;b&gt;&quot;청소&quot;&lt;/b&gt;라는 행위를 &lt;b&gt;&quot;학생&quot;&lt;/b&gt;에게 위임해주는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/llV9w/btrkK7rETNB/A1hUOMctjpO0xTO6iTdemK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/llV9w/btrkK7rETNB/A1hUOMctjpO0xTO6iTdemK/img.png&quot; data-alt=&quot;선생님과 학생간의 위임 관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/llV9w/btrkK7rETNB/A1hUOMctjpO0xTO6iTdemK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FllV9w%2FbtrkK7rETNB%2FA1hUOMctjpO0xTO6iTdemK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;121&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;선생님과 학생간의 위임 관계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Delegation 구현 (feat. 상호 의존성이 높은 케이스)&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;그렇다면 이 예시로 한 번 delegation을 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 선생님 객체를 만들어보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636784343825&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Teacher {
    // 청소 구역을 나타낸다 (구역명: 현재 청소여부)
    var cleaningArea: [String: Bool]
    // 청소를 시킬 학생을 지정
    var student: Student
    
    // 청소 구역과 학생을 할당한다
    init(cleaningArea: [String: Bool], student: Student) {
        self.cleaningArea = cleaningArea
        self.student = student
        self.student.delegator = self
    }
    
    // 오늘 청소해야하는 구역명을 알려준다
    func notifyTodayCleanArea() -&amp;gt; [String] {
        return cleaningArea.keys.map { String($0) }
    }
}&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;선생님(Teacher)은 청소 구역을 나누고, 정해진 청소구역을 특정 학생에게 알려주는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 Teacher 인스턴스가 생성되는 시점에 바로 위임자-대리인 관계가 형성되도록 init() 메서드 내부에 위임자를 정해주는 로직이 들어가있다.&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;그럼 학생(Student)는 무엇을 해야할까? 청소다. 청소 구역을 할당 받으면 청소를 하고, 청소 구역을 완료했다는 의미로 기존에 false이던 값을 true로 바꿔주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636784474157&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Student {
    // 일을 시킬 수 있는 사람은 선생님(Teacher)이다.
    var delegator: Teacher?
    
    // 청소 시작!
    func clean() {
        // 청소 구역 할당 받기
        let todayCleanTargetArea = delegator?.notifyTodayCleanArea()
        
        // 청소를 마치고 청소 구역을 청소했다는 의미로 true로 값을 변경해준다
        todayCleanTargetArea?.forEach { area in
            delegator?.cleaningArea.updateValue(true, forKey: area)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위임의 관계에서 봤을 때 학생은 대리인이고, 선생님은 위임자가 되는 것이다. 현재는 학생(대리인)과 선생님(위임자)이 서로를 알도록 구현하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 객체를 인스턴스화 해보고 청소를 진행해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636784625686&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1반 학생
let class1Student = Student()
// 1반 선생님
let class1Teacher = Teacher(cleaningArea: [&quot;복도&quot;:false, &quot;교실&quot;:false], student: class1Student)

// 1반 학생 청소 시작
class1Student.clean()

// 1반 청소 구역 정보
class1Teacher.cleaningArea // [&quot;복도&quot;: true, &quot;교실&quot;: true]&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;중요한 부분은 Teacher 타입을 인스턴스화 했을 때, 1반 학생이 위임을 받은 순간부터 1반 선생님이 시키는 일을 할 수 있다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청소를 마치고, 청소 구역에 대한 정보를 알맞게 변경시킨 것도 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 위임을 해주고, 대리인은 대신 일을 해주게 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 더 나아가서 학창시절을 회상해보자...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청소는 학교에서만 하지 않았다..! 집에서도 청소를 할 수 있는데, 그 때는 청소를 시키는 객체가 선생님이 아닌 부모님이 될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 현재 코드를 재사용 할 수 있을까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;왜냐하면 현재 Teacher라는 클래스 타입이어야 Student 타입에게 청소 구역을 알려줄 수 있기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 선생님이 학생에게만 청소를 시킬 수 있을까? 너무 꼰대같은 예시이지만 학교 경비 아저씨에게도 청소를 시킬 수 있다고 생각해보자.(예시는 예시일 뿐, 평등한 세상을 추구합니다... &amp;zwj;♂️)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에 현재 Teacher 타입은 Student 타입에게만 청소를 시킬 수 있기에, 다른 타입의 객체에게는 청소를 시킬 수 없게 된다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선생님은 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;학생&quot;&lt;/b&gt;&lt;/span&gt;에게만 청소를 시킬 수 있다&lt;/li&gt;
&lt;li&gt;학생은 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;선생님&quot;&lt;/b&gt;&lt;/span&gt;이 시키는 청소만 할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문장만 봐도 서로 많이 의존하고 있음을 알 수 있다. 그렇다면 이러한 의존성을 낮춰주기 위해서는 어떻게 해야할까??&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Delegation 구현 (feat. 상호 의존성을 낮춘 케이스)&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;추상 개념&quot;&lt;/b&gt;&lt;/span&gt;에 의존해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 개념이란 무엇일까? 현재 예시에 대입을 해보고 생각해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지금까지 청소를 시킬 수 있는 선생님과 청소를 할 수 있는 학생이라는 관계에 대해서 이야기해봤다.&amp;nbsp;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;누구인지&lt;/b&gt;&lt;b&gt;&quot;&lt;/b&gt;&lt;/span&gt;가 중요한게 아니라 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;어떤 행동&quot;&lt;/b&gt;&lt;/span&gt;을 할 수 있는지가 보다 더 중요하다는 것을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;청소를 할 수 있는&lt;/li&gt;
&lt;li&gt;청소를 시킬 수 있는&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화를 해보면 이 두 가지 추상 개념을 얻을 수 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선생님이 학생에게 청소를 시킨다는 상황을 다른 시선으로 바라보면, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;청소를 시킬 수 있는&lt;/b&gt;&lt;/span&gt;&amp;nbsp;사람이 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;청소를 할 수 있는&lt;/b&gt;&lt;/span&gt; 사람에게 청소를 시킨다고 볼 수 있는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그러면 이제 이 추상 개념을 코드로 나타내보고 각 객체들을 추상 개념에 의존하도록 수정해주면 기존의 의존성을 낮춰 볼 수 있을 것이다.&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;Swift에서 추상 개념이란, 거의&amp;nbsp; protocol이라고 생각해도 좋을 것 같다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 추상 개념들을 코드로 나타내보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636786925658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 청소를 할 수 있는
protocol Cleanable {
    func clean()
}

// 청소를 시킬 수 있는
protocol CleanOrderable {
    var cleaningArea: [String: Bool] { get set }
    
    func notifyTodayCleanArea() -&amp;gt; [String]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 부모님 클래스, 선생님 클래스를 만들어도 &quot;CleanOrderable&quot; 프로토콜만 채택해준다면 청소를 시킬 수 있는 역할과 책임을 얻을 수 있다!! 학생, 청소부 등등도 마찬가지로 &quot;Cleanable&quot; 프로토콜을 채택하는 순간 청소를 할 수 있는 역할과 책임을 얻게 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그러면 본인이 선생님 뿐만 아니라 부모님에게도 청소를 요청 받을 수 있도록 코드를 만들어보려면 어떻게 해야할까? 우선 선생님과 부모님 클래스를 각각 만들어보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636787446384&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Teacher: CleanOrderable {
    var cleaningArea: [String : Bool]
    var cleaner: Cleanable?
    var myClass: String
    
    init(area: [String : Bool], myClass: String) {
        self.cleaningArea = area
        self.myClass = myClass
    }
    
    func notifyTodayCleanArea() -&amp;gt; [String] {
        return cleaningArea.keys.map { String($0) }
    }
}

class Parent: CleanOrderable {
    var cleaningArea: [String : Bool]
    var cleaner: Cleanable?
    var dueDate: String
    
    init(area: [String : Bool], dueDate: String) {
        self.cleaningArea = area
        self.dueDate = dueDate
    }
    
    func notifyTodayCleanArea() -&amp;gt; [String] {
        return cleaningArea.keys.map { String($0) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Teacher은 myClass라는 프로퍼티를 추가로 가질 수 있고, Parent는 dueDate라는 프로퍼티를 추가로 가지도록 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 아무 기능을 하지는 않지만, 실제로 서로 다른 객체에서 시킬 수 있다는 상황을 가정하기 위해서 일부 다른 속성을 넣어줬다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 청소를 할 수 있는 객체들을 만들어 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청소를 할 수 있는 객체로는 Student와 Brother을 만들어보자. 둘 다 Cleanable 프로토콜을 채택하도록 하고, Brother 객체에는 청소 거부 의사를 나태낼 수 있는 메서드를 하나 추가해봤다. (이 또한 마찬가지로 서로 다른 객체가 특정 위임자가 시키는 일을 할 수 있다는 것을 보여주기 위해 추가한 것이다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636787737709&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Student: Cleanable {
    var cleanDelegator: CleanOrderable?
    
    func clean() {
        let todayCleanTargetArea = cleanDelegator?.notifyTodayCleanArea()
        
        todayCleanTargetArea?.forEach { area in
            cleanDelegator?.cleaningArea.updateValue(true, forKey: area)
        }
    }
}

class Brother: Cleanable {
    var cleanDelegator: CleanOrderable?
    
    func refuseCleaning() {
        print(&quot;하기 싫은데...&quot;)
    }
    
    func clean() {
        let todayCleanTargetArea = cleanDelegator?.notifyTodayCleanArea()
        
        todayCleanTargetArea?.forEach { area in
            cleanDelegator?.cleaningArea.updateValue(true, forKey: area)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 모든 준비가 되었다. 청소를 할 수 있고, 청소를 시킬 수 있는 객체들이 이제 있기에 서로가 서로에게 청소를 시키고, 청소를 할 수 있게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2024&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sqiSN/btrkJnhvM2a/wNU0X3kn2NC2rYZlKH93hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sqiSN/btrkJnhvM2a/wNU0X3kn2NC2rYZlKH93hK/img.png&quot; data-alt=&quot;위임자-대리인 관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sqiSN/btrkJnhvM2a/wNU0X3kn2NC2rYZlKH93hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsqiSN%2FbtrkJnhvM2a%2FwNU0X3kn2NC2rYZlKH93hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;337&quot; data-origin-width=&quot;2024&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위임자-대리인 관계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 더 이상 무서울게 없다!!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;CleanOrderable 프로토콜을 채택하기만 하면 일을 시킬 수 있는 존재가 되고, Cleanable 프로토콜을 채택하기만 하면 청소를 할 수 있는 존재가 된다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그러면 이제 서로에게 위임하고, 위임 받은 일을 하는 과정을 코드로 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636788208358&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let teacher = Teacher(area: [&quot;복도&quot;: false, &quot;교실&quot;: false], myClass: &quot;1반&quot;)
let parent = Parent(area: [&quot;니 방&quot;: false], dueDate: &quot;오늘 저녁까지&quot;)

let student = Student()
let brother = Brother()

student.cleanDelegator = teacher
student.clean()
teacher.cleaningArea // [&quot;복도&quot;: true, &quot;교실&quot;: true]

student.cleanDelegator = parent
student.clean()
parent.cleaningArea // [&quot;니 방&quot;: true]

// teacher와 parent가 정한 청소 구역이 초기화되었다고 생각해보자.

brother.cleanDelegator = teacher
brother.clean()
teacher.cleaningArea // [&quot;복도&quot;: true, &quot;교실&quot;: true]

brother.cleanDelegator = parent
brother.clean()
parent.cleaningArea // [&quot;니 방&quot;: true]&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;아까는 무조건 서로의 클래스의 인스턴스이어야 위임자-대리인의 관계가 성립했었으나, 지금은 어떠한 객체에 의존하는게 아니라 추상개념인 protocol에 의존함으로써 상호 의존도를 낮춘 모습을 볼 수 있다!&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;Delegation의 무궁무진함&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;이렇게 단순하게 객체들 간에 어떤 일을 대신 해줄 수도 있고, 실제 iOS에 접해서 생각해보면 VC 사이에서 대신 어떤 행위를 해준다거나, 데이터를 전달해 줄 수도 있을 것이다.&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;하지만 결국에는 근본 개념이 &quot;위임&quot;이라는 것이기 때문에, 근본 개념만 이해한다면 다른 delegation 또한 쉽게 이해해보고 응용해 볼 수 있을 것이다!&lt;/p&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와 TableViewCell 사이에서 delegtaion을 통해 대신 원하는 일을 해주는 그런 케이스들도 볼 수 있다. (&lt;a title=&quot;[iOS] UITableViewCell내 UIButton 액션이 작동하지 않을 때 (feat. tag/delegate/closure)  &quot; href=&quot;https://leechamin.tistory.com/500?category=941561&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://leechamin.tistory.com/500?category=941561&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;그러니 의존성을 낮추기 위한 시도가 필요한 상황에서 delegation을 떠올려봐도 좋을 것 같다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 delegation에 대해 정리를 해봤다!&lt;/p&gt;
&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;zwj;♂️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 초반에는 이해도 못하고 delegation 패턴을 사용했던 나를 반성하며...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 근본, 원리를 완벽하게 이해하고 설명해보고, 응용해보는 방식으로 학습해야겠다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끄읕&lt;/p&gt;</description>
      <category> &amp;zwj; iOS 공부/iOS &amp;amp; Swift</category>
      <category>delegate 패턴</category>
      <category>Delegation</category>
      <category>delegation 패턴</category>
      <category>swift</category>
      <category>위임자-대리자</category>
      <author>개발차</author>
      <guid isPermaLink="true">https://leechamin.tistory.com/550</guid>
      <comments>https://leechamin.tistory.com/550#entry550comment</comments>
      <pubDate>Sat, 13 Nov 2021 16:34:43 +0900</pubDate>
    </item>
  </channel>
</rss>