<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>HANJUN iOS</title>
    <link>https://h4njun.tistory.com/</link>
    <description>Swift, iOS</description>
    <language>ko</language>
    <pubDate>Tue, 7 Jul 2026 13:02:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>H4NJUN</managingEditor>
    <image>
      <title>HANJUN iOS</title>
      <url>https://tistory1.daumcdn.net/tistory/5098445/attach/542b9c45c35c44b6978353608d2571d8</url>
      <link>https://h4njun.tistory.com</link>
    </image>
    <item>
      <title>Accessibility - VoiceOver</title>
      <link>https://h4njun.tistory.com/entry/Accessibility-VoiceOver</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 &lt;b&gt;Accessiblity&lt;/b&gt;, 그중에서도 &lt;b&gt;VoiceOver&lt;/b&gt;에 관하여 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Accessibility&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649679148124&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;Introduction - Accessibility - Human Interface Guidelines - Apple Developer&quot; data-og-description=&quot;Accessibility People use Apple&amp;rsquo;s accessibility features, such as reduced transparency, VoiceOver, and increased text size, to personalize how they interact with their devices in ways that work for them. An accessible app supports such personalizations by&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&quot; data-og-url=&quot;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHi3ZC/hyN0hTz4kg/xxqEZIfFkhWhi8mhhiA6lK/img.png?width=316&amp;amp;height=684&amp;amp;face=0_0_316_684,https://scrap.kakaocdn.net/dn/AxBlx/hyN0rokQOx/C0Czke6LGraq3PKRBAv1G0/img.png?width=316&amp;amp;height=684&amp;amp;face=0_0_316_684&quot;&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHi3ZC/hyN0hTz4kg/xxqEZIfFkhWhi8mhhiA6lK/img.png?width=316&amp;amp;height=684&amp;amp;face=0_0_316_684,https://scrap.kakaocdn.net/dn/AxBlx/hyN0rokQOx/C0Czke6LGraq3PKRBAv1G0/img.png?width=316&amp;amp;height=684&amp;amp;face=0_0_316_684');&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;Introduction - Accessibility - Human Interface Guidelines - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Accessibility People use Apple&amp;rsquo;s accessibility features, such as reduced transparency, VoiceOver, and increased text size, to personalize how they interact with their devices in ways that work for them. An accessible app supports such personalizations by&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;Accessibility는 우리말로 &lt;b&gt;접근성&lt;/b&gt;이라는 뜻을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GzYOS/btry8QbBVFj/3X5m2BQ1kGPAH4Zv7xm460/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GzYOS/btry8QbBVFj/3X5m2BQ1kGPAH4Zv7xm460/img.png&quot; data-alt=&quot;네이버 특수교육학 용어사전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GzYOS/btry8QbBVFj/3X5m2BQ1kGPAH4Zv7xm460/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGzYOS%2Fbtry8QbBVFj%2F3X5m2BQ1kGPAH4Zv7xm460%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;246&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;246&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;간단히 말하자면 &lt;b&gt;어떤 환경에서도 불편함 없이 서비스를 이용할 수 있는 정도&lt;/b&gt;를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 접근성이 좋은 앱은 모든 사용자에게 최선의 사용자 경험을 제공하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이미 사용자로서 여러 가지 접근성 요소들을 경험하고 있습니다.&lt;/p&gt;
&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;b&gt;햅틱 &lt;/b&gt;등이 그 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플이 단지 멋있는 화면을 위해서 다크 모드를 제공하고 있던 게 아니라는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들은 접근성을 향상시키기 위해서 다양한 요소을 적용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기의 설정에 따라 다른 글자크기를 적용하는 &lt;b&gt;Dynamic Type&lt;/b&gt;, 시력이 좋지 않은 분들을 위해 화면의 콘텐츠를 읽어주는 &lt;b&gt;VoiceOver &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;Dynamic Type은 UI에 많은 영향을 끼치기 때문에 적용시키기 어려운 부분이 있을 수 있지만 VoiceOver는 비교적 간단하게 적용시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터는 VoiceOver에 대해서 알아보겠습니다.&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;2. VoiceOver&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.apple.com/accessibility/vision/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.apple.com/accessibility/vision/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649682055395&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;Accessibility - Vision&quot; data-og-description=&quot;Apple products and services include built-in features for a range of vision needs. Find the right feature for you to make something wonderful.&quot; data-og-host=&quot;www.apple.com&quot; data-og-source-url=&quot;https://www.apple.com/accessibility/vision/&quot; data-og-url=&quot;https://www.apple.com/accessibility/vision/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3W79E/hyN1CaKiR0/M8IQeoeDFHzeBNwK38Z3kk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.apple.com/accessibility/vision/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.apple.com/accessibility/vision/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3W79E/hyN1CaKiR0/M8IQeoeDFHzeBNwK38Z3kk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Accessibility - Vision&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Apple products and services include built-in features for a range of vision needs. Find the right feature for you to make something wonderful.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VoiceOver는 사용자가 화면을 볼 수 없는 상황에도 iPhone을 사용할 수 있도록 도와주는 &lt;b&gt;제스처 기반의 화면 읽기 도구&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 -&amp;gt; 손쉬운 사용 -&amp;gt; VoiceOver에서 활성화시키거나, Siri에게 켜달라고 부탁함으로써 활성화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JdvHU/btry8xXrd7c/RuuecqTurKG2VvFtORyfCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JdvHU/btry8xXrd7c/RuuecqTurKG2VvFtORyfCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JdvHU/btry8xXrd7c/RuuecqTurKG2VvFtORyfCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJdvHU%2Fbtry8xXrd7c%2FRuuecqTurKG2VvFtORyfCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;970&quot; height=&quot;768&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 VoiceOver를 활성화하신 분들은 당황할 수 있는데요(제가 그랬습니다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했다시피 VoiceOver는 제스처 기반의 화면 읽기 도구이기 때문에 제스처가 기존과 완전히 달라지게 됩니다.&lt;/p&gt;
&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://support.apple.com/ko-kr/guide/iphone/iph3e2e2281/15.0/ios/15.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기서&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;간단하게 탭과 쓸어 넘기기만 다루고 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 한 번 터치하게 되면 터치된 영역에 존재하는 항목을 선택하고 읽어줍니다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 두 번 터치하게 되면 현재 선택된 항목을 활성화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 좌우로 쓸어 넘기면 이전, 다음 항목을 가리키고 읽어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 &lt;b&gt;로터&lt;/b&gt;라는 것이 존재하는데 좀 더 아래서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. VoiceOver 적용 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 UIKit에서 VoiceOver를 적용하는 방법에 대해서 알아보겠습니다.&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;1.&amp;nbsp;isAccessibilityElement&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해당 요소가 접근성 요소인지를 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit이 제공하는 대부분의 View들은 이 속성을 true로 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;UIImageView&lt;/b&gt;와 같이 기본값이 false인 View들이 존재하기 때문에 해당 View의 접근성 요소를 적용하기 위해서는 이 속성을 true로 변경해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값이 true면 VoiceOver 실행 시 해당 요소를 선택할 수 있습니다. false일 경우에는 선택이 되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649688950971&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let label: UILabel = UILabel()
label.text = &quot;Hi&quot;
label.isAccessibilityElement = false // VoiceOver 실행 시 선택 안됨&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. accessibilityLabel&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소가 선택되었을 때 읽어질 문자열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;label이나 button에 title을 추가한 경우에는 자동으로 해당 문자열이 accessibilityLabel이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649690455131&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let label: UILabel = UILabel()
label.text = &quot;Hi&quot;
label.accessibilityLabel = &quot;안녕&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baes7B/btry5Ce5kMr/K6jS4QkNB3naYsqJxKgofK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baes7B/btry5Ce5kMr/K6jS4QkNB3naYsqJxKgofK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baes7B/btry5Ce5kMr/K6jS4QkNB3naYsqJxKgofK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbaes7B%2Fbtry5Ce5kMr%2FK6jS4QkNB3naYsqJxKgofK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;803&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. accessibilityValue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소의 상태를 나타내는 문자열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField 또는 Slider와 같이 동적으로 변하는 상태 값을 가지고 있을 경우 해당 값을 읽어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accessibilityLabel이 해당 요소가 어떤 요소인지를 나타내는 문자열이라면 Value는 해당 요소의 상태를 알려주는 문자열입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649692939778&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; let slider: UISlider = UISlider()
 slider.accessibilityLabel = &quot;크기&quot;
 // slider는 accessibilityValue를 기본값으로 가지고 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmK0QY/btry6u1KqxS/WVURRhGq39CQQKjf1ke4VK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmK0QY/btry6u1KqxS/WVURRhGq39CQQKjf1ke4VK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmK0QY/btry6u1KqxS/WVURRhGq39CQQKjf1ke4VK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmK0QY%2Fbtry6u1KqxS%2FWVURRhGq39CQQKjf1ke4VK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;805&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;961&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. accessibilityHint&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accessibilityLabel만으로 해당 요소가 어떤 역할을 하는지 완벽하게 설명하기 어려울 때 부가적인 설명을 위한 문자열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서는 Hint에 동작과 어떤 View인지를 포함하지 말라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;s&gt;연락처를 추가하려면 버튼을 탭 하세요&lt;/s&gt;&lt;/i&gt;보다는 &lt;i&gt;연락처 추가&lt;/i&gt;처럼 간단하게 작성해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649694089292&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let slider: UISlider = UISlider()
slider.accessibilityLabel = &quot;음량&quot;
slider.accessibilityHint = &quot;음악 음량 조절&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mAeXf/btry7Muf4Cn/uLYVdbYdt3ijD0eC9SSrK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mAeXf/btry7Muf4Cn/uLYVdbYdt3ijD0eC9SSrK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mAeXf/btry7Muf4Cn/uLYVdbYdt3ijD0eC9SSrK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmAeXf%2Fbtry7Muf4Cn%2FuLYVdbYdt3ijD0eC9SSrK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;960&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. accessibilityTraits&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소의 특징을 나타내는 속성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Button같이 특징을 가지고 있는 View들의 경우 기본 값으로 요소의 특징에 맞는 값을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;963&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J9VuQ/btrzbfcnHu2/dnsYSbYiVwQh1KfoIkwcA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J9VuQ/btrzbfcnHu2/dnsYSbYiVwQh1KfoIkwcA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J9VuQ/btrzbfcnHu2/dnsYSbYiVwQh1KfoIkwcA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ9VuQ%2FbtrzbfcnHu2%2FdnsYSbYiVwQh1KfoIkwcA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;963&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;963&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accessibilityTraits는 여러 개의 값을 가질 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649748927748&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;label.setTitle(&quot;add&quot;, for: .normal)
label.setImage(UIImage(systemName: &quot;plus&quot;), for: .normal)
label.accessibilityTraits.insert(.image)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insert()와 remove()를 통해 추가, 제거를 할 수 있습니다.&lt;/p&gt;
&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/uiaccessibility/uiaccessibilitytraits&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649748998390&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/uiaccessibility/uiaccessibilitytraits&quot; data-og-url=&quot;https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서 볼 수 있듯이 accessibilityTraits는 매우 많은 값을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 요소가 어떻게 동작하는지 잘 고려해서 accessibilityTraits를 적절하게 지정해야 합니다.&lt;/p&gt;
&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;b&gt;header&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;아까 VoiceOver에는 &lt;b&gt;로터&lt;/b&gt;라는 기능이 있다고 말씀드렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/HT204783&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://support.apple.com/ko-kr/HT204783&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649755421614&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;iPhone, iPad 및 iPod touch의 VoiceOver 로터에 관하여&quot; data-og-description=&quot;VoiceOver 로터를 사용하여 VoiceOver의 작동 방식을 변경할 수 있습니다. VoiceOver 음량 또는 말하기 속도를 변경하고 화면에서 다음 항목으로 이동하는 등의 다양한 작업을 수행할 수 있습니다.&quot; data-og-host=&quot;support.apple.com&quot; data-og-source-url=&quot;https://support.apple.com/ko-kr/HT204783&quot; data-og-url=&quot;https://support.apple.com/ko-kr/HT204783&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/beNBHT/hyN1G5LUFh/fhT5ZHKKh9vZJ4SF1I17fK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dIqcR9/hyN1ChYSDO/bzlxGuMPQ59O3Zh20A2Akk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/e4klq/hyN0jxB1Vo/3X1lzpcMZ2CdByWfV3XuVk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/HT204783&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://support.apple.com/ko-kr/HT204783&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/beNBHT/hyN1G5LUFh/fhT5ZHKKh9vZJ4SF1I17fK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dIqcR9/hyN1ChYSDO/bzlxGuMPQ59O3Zh20A2Akk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/e4klq/hyN0jxB1Vo/3X1lzpcMZ2CdByWfV3XuVk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;iPhone, iPad 및 iPod touch의 VoiceOver 로터에 관하여&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;VoiceOver 로터를 사용하여 VoiceOver의 작동 방식을 변경할 수 있습니다. VoiceOver 음량 또는 말하기 속도를 변경하고 화면에서 다음 항목으로 이동하는 등의 다양한 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;support.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;VoiceOver를 활성화시킨 상태에서 두 손가락으로 돌리면 로터가 등장합니다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 로터를 통해서 VoiceOver의 동작 방식을 변경시킬 수 있습니다.&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;&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;앱스토어의 예시를 보겠습니다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;/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;근데 이렇게 머리말 속성을 부여하는 값이 바로 header입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CollectionView나 TableView의 HeaderView를 만든다고 해서 머리말로 설정되지 않습니다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;pre id=&quot;code_1649755308405&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyCollectionViewHeader: UICollectionReusableView {
    private lazy var label: UILabel =  {
        let label = UILabel()
        return label
    }()
 
     func configureAccessibility() {
        self.label.accessibilityTraits = .header // 헤더 설정
    }
    
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 accessibilityTraits를 header로 설정하여야 머리말로서 동작합니다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Accessibility와 VoiceOver에 대해서 알아보았습니다.&lt;/p&gt;
&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;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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://www.apple.com/accessibility/&quot;&gt;https://www.apple.com/accessibility/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649681738280&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;Accessibility&quot; data-og-description=&quot;Every Apple product and service includes built-in accessibility features designed for you to make something wonderful.&quot; data-og-host=&quot;www.apple.com&quot; data-og-source-url=&quot;https://www.apple.com/accessibility/&quot; data-og-url=&quot;https://www.apple.com/accessibility/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkelr7/hyN0pxi50P/iFKtdRDUReTWK5zF1Mwuq0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cLrMcK/hyN1xHgS9W/baULKNn2G1Rjc9ZLI6Nt90/img.png?width=1500&amp;amp;height=1060&amp;amp;face=0_0_1500_1060,https://scrap.kakaocdn.net/dn/grBxS/hyN1IPy3Wu/n0qT4lMDAUdfTKQpQQW5n0/img.jpg?width=888&amp;amp;height=1060&amp;amp;face=0_0_888_1060&quot;&gt;&lt;a href=&quot;https://www.apple.com/accessibility/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.apple.com/accessibility/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkelr7/hyN0pxi50P/iFKtdRDUReTWK5zF1Mwuq0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cLrMcK/hyN1xHgS9W/baULKNn2G1Rjc9ZLI6Nt90/img.png?width=1500&amp;amp;height=1060&amp;amp;face=0_0_1500_1060,https://scrap.kakaocdn.net/dn/grBxS/hyN1IPy3Wu/n0qT4lMDAUdfTKQpQQW5n0/img.jpg?width=888&amp;amp;height=1060&amp;amp;face=0_0_888_1060');&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;Accessibility&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Every Apple product and service includes built-in accessibility features designed for you to make something wonderful.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플의 접근성 페이지를 확인하면 더 나은 접근성을 위해서 얼마나 고민하고 있는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;</description>
      <category>iOS</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/32</guid>
      <comments>https://h4njun.tistory.com/entry/Accessibility-VoiceOver#entry32comment</comments>
      <pubDate>Tue, 12 Apr 2022 18:40:19 +0900</pubDate>
    </item>
    <item>
      <title>Model-View-ViewModel(MVVM)</title>
      <link>https://h4njun.tistory.com/entry/Model-View-ViewModelMVVM</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 시간에는 MVP 패턴을 통해서 Controller의 역할을 줄이고, UIKit과 분리되어 테스트 가능한 Presenter를 만들어보았습니다.&lt;/p&gt;
&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;Presenter가 View를 &amp;nbsp;알고 있다&lt;/b&gt;는 사실입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 Protocol을 통해 참조를 하고 있더라도 View에 대한 Presenter의 종속성은 남아 있습니다.&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;Presenter가 View를 모르게 하는 방법..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 우리는 MVC를 공부하면서 비슷한 개념을 경험해봤습니다. 감이 오시나요??&lt;/p&gt;
&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;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. MVC에서?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u446Y/btryI5BAnqk/leURo7n9HNdbpAQBqZkQq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u446Y/btryI5BAnqk/leURo7n9HNdbpAQBqZkQq0/img.png&quot; data-alt=&quot;Cocoa MVC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u446Y/btryI5BAnqk/leURo7n9HNdbpAQBqZkQq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu446Y%2FbtryI5BAnqk%2FleURo7n9HNdbpAQBqZkQq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;879&quot; height=&quot;367&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cocoa MVC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC를 학습하면서 위의 그림을 모두 보셨을 겁니다.&lt;/p&gt;
&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/84BGI/btryI5auUSc/OMgRowV3hlwIkFJ0pu2IK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/84BGI/btryI5auUSc/OMgRowV3hlwIkFJ0pu2IK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/84BGI/btryI5auUSc/OMgRowV3hlwIkFJ0pu2IK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F84BGI%2FbtryI5auUSc%2FOMgRowV3hlwIkFJ0pu2IK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;474&quot; height=&quot;384&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이곳입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC를 알아볼 때 Model이 Controller를 직접 알면 안 된다라고 공부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;관찰자 패턴&lt;/b&gt;을 통해서 해당 문제를 해결했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 View랑 Presenter도 비슷한 방법을 사용하면 되지 않을까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등장하게 된 것이 바로 &lt;b&gt;MVVM(Model-View-ViewModel)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. MVVM&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDK4qP/btryJv7AWzR/5VFWZXMOgUjwLRzk7hfYcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDK4qP/btryJv7AWzR/5VFWZXMOgUjwLRzk7hfYcK/img.png&quot; data-alt=&quot;Reference - https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDK4qP/btryJv7AWzR/5VFWZXMOgUjwLRzk7hfYcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDK4qP%2FbtryJv7AWzR%2F5VFWZXMOgUjwLRzk7hfYcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;218&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Reference - https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM &amp;nbsp;역시 MVC의 Controller, MVP의 Presenter와 같이 Model과 View의 중간자 역할을 하는 &lt;b&gt;ViewModel&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;하지만 ViewModel은 Controller, Presenter와 다르게 &lt;b&gt;View를 전혀 알지 못합니다.&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;View와 ViewModel을 연결해 서로를 동기화하는 것을&amp;nbsp;&lt;b&gt;Data Binding&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;Data Binding을 하는 방법은 여러가지(Combine, RxSwift)가 있지만, 이번에는 클로저와 didSet을 통해 간단하게 관찰 가능한 객체를 만들어보도록 하겠습니다.&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;3. Observable&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1649344703156&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Observable&amp;lt;T&amp;gt; {
    
    var value: T {
        didSet {
            self.listener?(value)
        }
    }
    
    private var listener: ((T) -&amp;gt; Void)?
    
    init(_ value: T) {
        self.value = value
    }
    
    func bind(_ closure: @escaping (T) -&amp;gt; Void) {
        self.listener = closure
        closure(value)
    }
}&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;value의 값이 변경되면 bind()를 통해 전달받은 클로저를 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 클로저는 아마 대부분 UI관련 클로저겠죠??&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 앱을 통해서 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b45o46/btryI4iKhhk/6a0sR4ktPIN8c6e3xSJgyk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b45o46/btryI4iKhhk/6a0sR4ktPIN8c6e3xSJgyk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b45o46/btryI4iKhhk/6a0sR4ktPIN8c6e3xSJgyk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b45o46/btryI4iKhhk/6a0sR4ktPIN8c6e3xSJgyk/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;296&quot; height=&quot;640&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 버튼과 - 버튼을 통해 Label의 값을 변경하는 앱입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Model&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 앱에서 Model은 Label의 표시될 값과 숫자의 증감을 위한 로직입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649395059982&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Amount: NSObject {

    @objc dynamic var number: Int
    
    override init() {
        self.number = 0
    }
    
    func increase() {
        self.number += 1
    }
    
    func decrease() {
        self.number -= 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- View&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM에서는 MVP와 마찬가지로 &lt;b&gt;ViewController를 View로 취급&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649395384841&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewController: UIViewController {
    
    private lazy var increaseButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: &quot;plus&quot;), for: .normal)
        button.addTarget(self, action: #selector(increaseButtonTouched(_sender:)), for: .touchUpInside)
        return button
    }()
    
    private lazy var decreaseButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: &quot;minus&quot;), for: .normal)
        button.addTarget(self, action: #selector(decreaseButtonTouched(_sender:)), for: .touchUpInside)
        return button
    }()
    
    private lazy var amountLabel: UILabel = {
        let label = UILabel()
        label.font = label.font.withSize(32)
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.configureUI()
    }
    
    
    func configureUI() {
        // layout 설정
    }
    
    @objc func increaseButtonTouched(_sender: UIButton) {
        //viewModel 메서드 호출
    }
    
    @objc func decreaseButtonTouched(_sender: UIButton) {
        //viewModel 메서드 호출
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 ViewModel을 만들지 않았으니 대략적인 뼈대만 작성하겠습니다.&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;- ViewModel&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM에서 &lt;b&gt;View는 ViewModel을 알고 있습니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 의존성을 줄여주기 위해 ViewModel Protocol을 정의하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel을 정의하는 방법에는 여러 가지 방법이 있습니다만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kudoleh/iOS-Clean-Architecture-MVVM&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Clean Architecture and MVVM on iOS&lt;/a&gt;에서는 Input과 Output을 분리해 정의하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Input은 ViewModel이 View로부터 전달받을 이벤트들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output은 ViewModel이 이벤트들을 처리한 후의 결과(View과 관찰할 속성)입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649396457796&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol ViewModelInput {
    func viewDidLoad()
    func didIncrease()
    func didDecrease()
}

protocol ViewModelOutput {
    var amountText: Observable&amp;lt;String&amp;gt; { get }
}

typealias ViewModel = ViewModelInput &amp;amp; ViewModelOutput&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;ViewController의 viewDidLoad()에서 호출될 메서드, 각 버튼을 눌렀을 때 호출될 메서드들이 Input이 되겠죠.&lt;/p&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가 Label에 표시할 값이 Output입니다.&lt;/p&gt;
&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_1649397715311&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DefaultViewModel: ViewModel {
    
    var amountText: Observable&amp;lt;String&amp;gt;
    
    private var amount: Amount
    private var observation: NSKeyValueObservation?
    
    init(amount: Amount) { // 의존성 주입
        self.amount = amount
        self.amountText = Observable(String(amount.number))
    }
    
    func viewDidLoad() {
        self.observation = self.amount.observe(\.number, options: .new) { object, change in
            guard let number = change.newValue else { return }
            self.amountText.value = String(number)
        }
    }
    
    func didIncrease() {
        self.amount.increase()
    }
    
    func didDecrease() {
        self.amount.decrease()
    }
}&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;amount를 생성자에서 초기화해도 되지만 의존성 주입을 통해서 의존성을 줄여주겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model인 amount의 상태변화는 KVO를 통해 관찰하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Binding 작업만 남았습니다.&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;- Data Binding&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController를 마저 작성하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649398651961&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//ViewController내에서 작성되는 코드입니다.

private var viewModel: ViewModel?

convenience init(viewModel: ViewModel) {
    self.init(nibName: nil, bundle: nil)
    self.viewModel = viewModel
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController는 ViewModel 프로토콜을 참조하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 ViewController를 생성할 때 viewModel을 주입받겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649398955317&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//ViewController에서 작성되는 코드입니다.

func bind() {
    self.viewModel?.amountText.bind { [weak self] value in
        self?.amountLabel.text = value
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view와 ViewModel을 Binding 할 bind() 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel의 amountText가 변경될 시 Label의 Text를 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649399099979&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func viewDidLoad() {
    super.viewDidLoad()
    self.configureUI()
    self.bind()
    self.viewModel?.viewDidLoad()
}

@objc func increaseButtonTouched(_sender: UIButton) {
    self.viewModel?.didIncrease()
}

@objc func decreaseButtonTouched(_sender: UIButton) {
    self.viewModel?.didDecrease()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 위치에서 viewModel의 함수를 호출합니다.&lt;/p&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를 생성할 때 Model, ViewModel을 생성해 주입하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 앱에서는 간단히 SceneDelegate에서 처리하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649399424936&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
 
        guard let windowScene = (scene as? UIWindowScene) else { return }
        self.window = UIWindow(windowScene: windowScene)
        
        let amount = Amount() // Model
        let viewModel = DefaultViewModel(amount: amount) // ViewModel
        let viewController = ViewController(viewModel: viewModel) // ViewController
        
        self.window?.rootViewController = viewController
        self.window?.makeKeyAndVisible()
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 앱을 통해 MVVM 패턴을 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM 역시 &lt;b&gt;테스트하기 좋은 구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel이 SwiftUI 또는 UIKit과 같은 UI 관련된 코드를 전혀 포함하기 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 ViewModel은 그 어떤 View도 직접 참조하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 &lt;b&gt;동일한 ViewModel을 다른 View에서 사용할 수 있다&lt;/b&gt;는 소리입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3r3Cj/btryOgvEc7D/kMrzeXySSvznfKKFPhpf9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3r3Cj/btryOgvEc7D/kMrzeXySSvznfKKFPhpf9K/img.png&quot; data-alt=&quot;View와 ViewModel은 n : 1 관계이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3r3Cj/btryOgvEc7D/kMrzeXySSvznfKKFPhpf9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3r3Cj%2FbtryOgvEc7D%2FkMrzeXySSvznfKKFPhpf9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;934&quot; height=&quot;292&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;View와 ViewModel은 n : 1 관계이다.&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;Int -&amp;gt; Amount(Model) -&amp;gt; Observable&amp;lt;String&amp;gt;(ViewModel) -&amp;gt; Label(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;또한 이번에 MVVM을 공부하면서 느낀 점은 정형화된 패턴이 없다는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 가지 자료를 찾아봤으나 자료마다 조금씩 다르게 MVVM을 설명하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 MVVM 패턴을 사용할 경우 팀원들 간의 조율을 통해 정확히 패턴을 인지하고 가야 할 것 같습니다.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ref&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fitzafful.medium.com/data-binding-in-mvvm-on-ios-714eb15e3913&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://fitzafful.medium.com/data-binding-in-mvvm-on-ios-714eb15e3913&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649344471938&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;Data Binding in MVVM on iOS&quot; data-og-description=&quot;You&amp;rsquo;ve come to realize your View Controller in your new project has become very huge&amp;hellip;&quot; data-og-host=&quot;fitzafful.medium.com&quot; data-og-source-url=&quot;https://fitzafful.medium.com/data-binding-in-mvvm-on-ios-714eb15e3913&quot; data-og-url=&quot;https://fitzafful.medium.com/data-binding-in-mvvm-on-ios-714eb15e3913&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzWYIH/hyNYIIPsNT/eoBWqUXiX4SkKgexpwAr60/img.png?width=1200&amp;amp;height=516&amp;amp;face=0_0_1200_516,https://scrap.kakaocdn.net/dn/bdndSo/hyNXnfcCWC/CrKFkA9LKxzEQ4TANTcELK/img.png?width=1400&amp;amp;height=602&amp;amp;face=0_0_1400_602&quot;&gt;&lt;a href=&quot;https://fitzafful.medium.com/data-binding-in-mvvm-on-ios-714eb15e3913&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fitzafful.medium.com/data-binding-in-mvvm-on-ios-714eb15e3913&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzWYIH/hyNYIIPsNT/eoBWqUXiX4SkKgexpwAr60/img.png?width=1200&amp;amp;height=516&amp;amp;face=0_0_1200_516,https://scrap.kakaocdn.net/dn/bdndSo/hyNXnfcCWC/CrKFkA9LKxzEQ4TANTcELK/img.png?width=1400&amp;amp;height=602&amp;amp;face=0_0_1400_602');&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;Data Binding in MVVM on iOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You&amp;rsquo;ve come to realize your View Controller in your new project has become very huge&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fitzafful.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649394540260&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;Clean Architecture and MVVM on iOS&quot; data-og-description=&quot;When we develop software it is important to not only use design patterns, but also architectural patterns. There are many different&amp;hellip;&quot; data-og-host=&quot;tech.olx.com&quot; data-og-source-url=&quot;https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&quot; data-og-url=&quot;https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/53XpD/hyNYJusHRl/lsBbX7wzqTIiNIlNtLXx11/img.jpg?width=1200&amp;amp;height=1145&amp;amp;face=0_0_1200_1145,https://scrap.kakaocdn.net/dn/1oeiY/hyNYKz8CLx/lsLrC7Ak7MQBFhLrhc2I5K/img.png?width=1100&amp;amp;height=456&amp;amp;face=0_0_1100_456,https://scrap.kakaocdn.net/dn/Vf8V8/hyNYPnViS8/VrSgngsdke1R1T5gEzJvKK/img.jpg?width=1400&amp;amp;height=1336&amp;amp;face=0_0_1400_1336&quot;&gt;&lt;a href=&quot;https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/53XpD/hyNYJusHRl/lsBbX7wzqTIiNIlNtLXx11/img.jpg?width=1200&amp;amp;height=1145&amp;amp;face=0_0_1200_1145,https://scrap.kakaocdn.net/dn/1oeiY/hyNYKz8CLx/lsLrC7Ak7MQBFhLrhc2I5K/img.png?width=1100&amp;amp;height=456&amp;amp;face=0_0_1100_456,https://scrap.kakaocdn.net/dn/Vf8V8/hyNYPnViS8/VrSgngsdke1R1T5gEzJvKK/img.jpg?width=1400&amp;amp;height=1336&amp;amp;face=0_0_1400_1336');&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;Clean Architecture and MVVM on iOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When we develop software it is important to not only use design patterns, but also architectural patterns. There are many different&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.olx.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://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649394573522&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;iOS Architecture Patterns&quot; data-og-description=&quot;Demystifying MVC, MVP, MVVM and VIPER&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; data-og-url=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ctmBeI/hyNYG5zxDq/aJOL8lEY8QCsr8WwcwzDDk/img.jpg?width=1200&amp;amp;height=400&amp;amp;face=0_0_1200_400,https://scrap.kakaocdn.net/dn/ouopU/hyNYI99Ig6/wlNSFb1m122E9dWudnaLKK/img.png?width=870&amp;amp;height=336&amp;amp;face=0_0_870_336,https://scrap.kakaocdn.net/dn/chqsQL/hyNYHi6SK8/twqWKvDXCfDa2tavxd0KV1/img.png?width=868&amp;amp;height=332&amp;amp;face=0_0_868_332&quot;&gt;&lt;a href=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ctmBeI/hyNYG5zxDq/aJOL8lEY8QCsr8WwcwzDDk/img.jpg?width=1200&amp;amp;height=400&amp;amp;face=0_0_1200_400,https://scrap.kakaocdn.net/dn/ouopU/hyNYI99Ig6/wlNSFb1m122E9dWudnaLKK/img.png?width=870&amp;amp;height=336&amp;amp;face=0_0_870_336,https://scrap.kakaocdn.net/dn/chqsQL/hyNYHi6SK8/twqWKvDXCfDa2tavxd0KV1/img.png?width=868&amp;amp;height=332&amp;amp;face=0_0_868_332');&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;iOS Architecture Patterns&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Demystifying MVC, MVP, MVVM and VIPER&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;&lt;a href=&quot;https://medium.com/@dev.omartarek/mvp-vs-mvvm-in-ios-using-swift-337884d4fc6f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/@dev.omartarek/mvp-vs-mvvm-in-ios-using-swift-337884d4fc6f&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649396397446&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;MVP VS MVVM in iOS using swift&quot; data-og-description=&quot;Introduction:&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@dev.omartarek/mvp-vs-mvvm-in-ios-using-swift-337884d4fc6f&quot; data-og-url=&quot;https://medium.com/@dev.omartarek/mvp-vs-mvvm-in-ios-using-swift-337884d4fc6f&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ce1pzY/hyNXuZ172k/nlJc6awqTSHKPJyugFSNQ1/img.jpg?width=1200&amp;amp;height=750&amp;amp;face=699_18_809_139,https://scrap.kakaocdn.net/dn/ctrgTV/hyNYICkIyp/KkCSOaTEhgk1R8h7vpRdZk/img.png?width=856&amp;amp;height=1512&amp;amp;face=0_0_856_1512,https://scrap.kakaocdn.net/dn/4UtrC/hyNYJ840oN/0U5EmfWvs5oalKTl4GgGZk/img.jpg?width=1200&amp;amp;height=750&amp;amp;face=699_18_809_139&quot;&gt;&lt;a href=&quot;https://medium.com/@dev.omartarek/mvp-vs-mvvm-in-ios-using-swift-337884d4fc6f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@dev.omartarek/mvp-vs-mvvm-in-ios-using-swift-337884d4fc6f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ce1pzY/hyNXuZ172k/nlJc6awqTSHKPJyugFSNQ1/img.jpg?width=1200&amp;amp;height=750&amp;amp;face=699_18_809_139,https://scrap.kakaocdn.net/dn/ctrgTV/hyNYICkIyp/KkCSOaTEhgk1R8h7vpRdZk/img.png?width=856&amp;amp;height=1512&amp;amp;face=0_0_856_1512,https://scrap.kakaocdn.net/dn/4UtrC/hyNYJ840oN/0U5EmfWvs5oalKTl4GgGZk/img.jpg?width=1200&amp;amp;height=750&amp;amp;face=699_18_809_139');&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;MVP VS MVVM in iOS using swift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Introduction:&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>Swift</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/31</guid>
      <comments>https://h4njun.tistory.com/entry/Model-View-ViewModelMVVM#entry31comment</comments>
      <pubDate>Fri, 8 Apr 2022 15:58:34 +0900</pubDate>
    </item>
    <item>
      <title>Model-View-Presenter(MVP)</title>
      <link>https://h4njun.tistory.com/entry/Model-View-PresenterMVP</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에는 MVC에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 역시 선배 개발자 분들의 수많은 고민이 담긴 훌륭한 아키텍처였지만, 해결하지 못한 문제들이 존재했습니다.&lt;/p&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. Controller의 역할이 너무 커진다.(Massive View Controller..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ViewController가 UIKit에 의존적이기 때문에 테스트하기 어렵다.&lt;/p&gt;
&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;Model-View-Presenter(MVP)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. MVP&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP는&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Controller가 UIKit과 의존적이기 때문에 테스트가 어려우니&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit에 독립적이면서 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;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7msu6/btryqh82NWw/7GDfMN3QU6FtmXD3BWCBhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7msu6/btryqh82NWw/7GDfMN3QU6FtmXD3BWCBhK/img.png&quot; data-alt=&quot;MVP&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7msu6/btryqh82NWw/7GDfMN3QU6FtmXD3BWCBhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7msu6%2Fbtryqh82NWw%2F7GDfMN3QU6FtmXD3BWCBhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;428&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MVP&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼핏 봐서는 MVC와 뭐가 다르지?라고 생각하실 수 있습니다.&lt;/p&gt;
&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;우선 첫 번째로 MVP에서는 &lt;b&gt;UIViewController&lt;/b&gt;를 Controller가 아닌 &lt;b&gt;View로 취급&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ESZoQ/btryn9KUymG/bzoqU3K5LgKFg0rCKDzPMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ESZoQ/btryn9KUymG/bzoqU3K5LgKFg0rCKDzPMK/img.png&quot; data-alt=&quot;UIViewController를 View로 취급&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ESZoQ/btryn9KUymG/bzoqU3K5LgKFg0rCKDzPMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FESZoQ%2Fbtryn9KUymG%2FbzoqU3K5LgKFg0rCKDzPMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;305&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UIViewController를 View로 취급&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;그리고 두 번째로 MVC와 달리 중간자 역할을 하는 &lt;b&gt;Presenter는 UIKIt에 완전히 독립적&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceUo4x/btryp4PCzYi/f6gwDvxJbaDKKncm9CLyh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceUo4x/btryp4PCzYi/f6gwDvxJbaDKKncm9CLyh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceUo4x/btryp4PCzYi/f6gwDvxJbaDKKncm9CLyh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceUo4x%2Fbtryp4PCzYi%2Ff6gwDvxJbaDKKncm9CLyh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;235&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, UIKit과 관련된 코드는 모두 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;b&gt;UI 관련된 코드와 비즈니스 로직이 완전히 분리&lt;/b&gt;되어 테스트하기 훨씬 용이한 구조가 되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 코드를 통해 직접 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/seHWF/btryoZnUndg/SKFgmFSh4jDtGrrKUzGW7K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/seHWF/btryoZnUndg/SKFgmFSh4jDtGrrKUzGW7K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/seHWF/btryoZnUndg/SKFgmFSh4jDtGrrKUzGW7K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/seHWF/btryoZnUndg/SKFgmFSh4jDtGrrKUzGW7K/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;238&quot; height=&quot;640&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 버튼을 누르면 값이 증가하고 - 버튼을 누르면 값이 감소하는 간단한 앱입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Model&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 앱에서 Model은 수량을 나타내기 위한 데이터입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649063042582&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Amount: NSObject {
    @objc dynamic var number: Int
    
    override init() {
        self.number = 0
    }
    
    func increase() {
        self.number += 1
    }
    
    func decrease() {
        self.number -= 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;View&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 ViewController가 View로서 행할 메서드를 정의한 프로토콜을 작성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 앱에서는 값이 변할 경우 그 값을 Label에 보여주기 위한 함수를 정의했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649063751090&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol CountView: AnyObject {
    func onValueChange(number: Int)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ViewController가 해당 프로토콜을 채택하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변한 값을 Label에 적용시켜주도록 구현하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649064324684&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension ViewController: CountView {
    func onValueChange(number: Int) {
        self.amountLabel.text = String(number)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Presenter&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Presenter 또한 Presenter로서 필요한 메서드들을 정의한 Protocol을 작성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로토콜은 위에서 정의한 View Protocol을 매개변수로 하는 생성자와 ViewController로부터 전달받은 이벤트들을 처리할 메서드들을 필요로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 앱에서는 +버튼과 -버튼이 눌렸을 때 동작할 메서드들을 정의했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649064838320&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol CountViewPresenter {
    init(view: CountView)
    func viewDidLoad()
    func increaseButtonTouched()
    func decreaseButtonTouched()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 프로토콜을 준수하는 Presenter를 만들겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649065813973&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CountPresenter: CountViewPresenter {
    private unowned var view: CountView
    
    private var amount: Amount!
    private var observation: NSKeyValueObservation?
    
    required init(view: CountView) {
        self.view = view
    }
    
    func viewDidLoad() {
        self.amount = Amount()
        self.view.onValueChange(number: 0)
        self.observation = self.amount.observe(\.number, options: .new) { object, change in
            guard let number = change.newValue else { return }
            self.view.onValueChange(number: number)
        }
    }
    
    func increaseButtonTouched() {
        self.amount.increase()
    }
    
    func decreaseButtonTouched() {
        self.amount.decrease()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의해야 할 점은 ViewController에서도 Presenter를 소유하고 있기 때문에 Presenter가 알고 있는 View는 순환 참조를 막기 위해서 Weak 또는 Unowned로 작성해야 합니다.&lt;/p&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController가 Presenter를 소유하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트가 발생하면 해당하는 Presenter의 메서드를 호출하도록 코드를 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649068627868&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewController: UIViewController {
    /*
    View 선언
    */
    
    var presenter: CountViewPresenter?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.presenter?.viewDidLoad()
    }
    
    @objc func increaseButtonTouched(_sender: UIButton) {
        self.presenter?.increaseButtonTouched()
    }
    
    @objc func decreaseButtonTouched(_sender: UIButton) {
        self.presenter?.decreaseButtonTouched()
    }
}

extension ViewController: CountView {
    func onValueChange(number: Int) {
        self.amountLabel.text = String(number)
    }
}&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;마지막으로 SceneDelegate와 같은 상위에서 ViewController를 생성할 때 Presenter 역시 생성해 주입하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649069034675&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        
        let view = ViewController()
        let presenter = CountPresenter(view: view)
        view.presenter = presenter
        
        self.window = UIWindow(windowScene: scene)
        self.window?.rootViewController = view
        self.window?.makeKeyAndVisible()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 &lt;a href=&quot;https://gist.github.com/hj56775/1bc09903de09d4204c5a9df205b0942b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이곳에서&lt;/a&gt; 확인하실 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 코드를 작성하면 Presenter가 View를 직접 알지 않고 Protocol을 통해 알고 있기 때문에 View의 Mock을 만들기 쉬운 구조입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649071136157&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CountViewMock: CountView {
    var number: Int = 0
    func onValueChange(number: Int) {
        self.number = number
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트만을 위한 Mock 객체를 만들고 테스트를 진행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649071166103&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//설명을 위해 작성된 코드입니다.
class PresenterTests: XCTestCase {
    
    private var presenter: CountPresenter!
    private var viewMock: CountViewMock!
    
    override func setUpWithError() throws {
        self.viewMock = CountViewMock()
        self.presenter = CountPresenter(view: viewMock)
        self.presenter.viewDidLoad()
    }

    override func tearDownWithError() throws {
        self.presenter = nil
        self.viewMock = nil
    }
    

    func test_increaseButton() {
        self.presenter.increaseButtonTouched()
        XCTAssertEqual(self.viewMock.number, 1, &quot;잘못된 값입니다.&quot;)
        self.presenter.increaseButtonTouched()
        XCTAssertEqual(self.viewMock.number, 2, &quot;잘못된 값입니다.&quot;)
    }
    
    func test_decreaseButton() {
        self.presenter.decreaseButtonTouched()
        XCTAssertEqual(self.viewMock.number, -1, &quot;잘못된 값입니다.&quot;)
        self.presenter.decreaseButtonTouched()
        XCTAssertEqual(self.viewMock.number, -2, &quot;잘못된 값입니다.&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC에서는 테스트하기 어렵던 Controller에 작성된 코드들을 UIKit과 무관한 Presenter에 작성함으로써 &lt;b&gt;훨씬 테스트하기 좋은 구조&lt;/b&gt;가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 보일러 플레이트 코드의 양이 늘어나 결과적으로 MVC에 비해서 &lt;b&gt;코드 양이 어마어마하게 늘어난다는 단점&lt;/b&gt;이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Presenter 역시도 직접 View에게 화면을 그리라고 지시합니다. 즉 &lt;b&gt;View와 Presenter가 1:1&lt;/b&gt; 관계를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 비슷한 UI일지라도 완전히 같지 않다면 새로운 Presenter를 필요로 하게 된다는 점입니다.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://betterprogramming.pub/implement-a-model-view-presenter-architecture-in-swift-5-dfa21bbb8e0b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://betterprogramming.pub/implement-a-model-view-presenter-architecture-in-swift-5-dfa21bbb8e0b&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649055242124&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;Implement a Model-View-Presenter Architecture in Swift 5&quot; data-og-description=&quot;Using the Realm database as a business logic layer&quot; data-og-host=&quot;betterprogramming.pub&quot; data-og-source-url=&quot;https://betterprogramming.pub/implement-a-model-view-presenter-architecture-in-swift-5-dfa21bbb8e0b&quot; data-og-url=&quot;https://betterprogramming.pub/implement-a-model-view-presenter-architecture-in-swift-5-dfa21bbb8e0b&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHbSvr/hyNUs8RZQp/EBkzZxcp6iFWz3wGWSCCf0/img.jpg?width=1200&amp;amp;height=799&amp;amp;face=0_0_1200_799,https://scrap.kakaocdn.net/dn/beRtX0/hyNVNwN7Uy/bhFjA1gGPpZiv4pSKL3pKK/img.png?width=1302&amp;amp;height=1300&amp;amp;face=0_0_1302_1300,https://scrap.kakaocdn.net/dn/gm9Wn/hyNUullQb5/yYFq3Tj0R05pcc8NKSOUJ1/img.jpg?width=1400&amp;amp;height=933&amp;amp;face=0_0_1400_933&quot;&gt;&lt;a href=&quot;https://betterprogramming.pub/implement-a-model-view-presenter-architecture-in-swift-5-dfa21bbb8e0b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://betterprogramming.pub/implement-a-model-view-presenter-architecture-in-swift-5-dfa21bbb8e0b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHbSvr/hyNUs8RZQp/EBkzZxcp6iFWz3wGWSCCf0/img.jpg?width=1200&amp;amp;height=799&amp;amp;face=0_0_1200_799,https://scrap.kakaocdn.net/dn/beRtX0/hyNVNwN7Uy/bhFjA1gGPpZiv4pSKL3pKK/img.png?width=1302&amp;amp;height=1300&amp;amp;face=0_0_1302_1300,https://scrap.kakaocdn.net/dn/gm9Wn/hyNUullQb5/yYFq3Tj0R05pcc8NKSOUJ1/img.jpg?width=1400&amp;amp;height=933&amp;amp;face=0_0_1400_933');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Implement a Model-View-Presenter Architecture in Swift 5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using the Realm database as a business logic layer&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;betterprogramming.pub&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://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649055251465&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;iOS Architecture Patterns&quot; data-og-description=&quot;Demystifying MVC, MVP, MVVM and VIPER&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; data-og-url=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkoMWq/hyNUuZVk9C/uYkFWnqSV7uL7ZeVRjhTm1/img.jpg?width=1200&amp;amp;height=400&amp;amp;face=0_0_1200_400,https://scrap.kakaocdn.net/dn/zVXGT/hyNVMScpNS/NLuCr7Cta5IP3kAgY7ojT1/img.png?width=870&amp;amp;height=336&amp;amp;face=0_0_870_336,https://scrap.kakaocdn.net/dn/bGohhh/hyNVRzcX8d/7HYCgIk6O8WIT6HoBXXX11/img.png?width=868&amp;amp;height=332&amp;amp;face=0_0_868_332&quot;&gt;&lt;a href=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkoMWq/hyNUuZVk9C/uYkFWnqSV7uL7ZeVRjhTm1/img.jpg?width=1200&amp;amp;height=400&amp;amp;face=0_0_1200_400,https://scrap.kakaocdn.net/dn/zVXGT/hyNVMScpNS/NLuCr7Cta5IP3kAgY7ojT1/img.png?width=870&amp;amp;height=336&amp;amp;face=0_0_870_336,https://scrap.kakaocdn.net/dn/bGohhh/hyNVRzcX8d/7HYCgIk6O8WIT6HoBXXX11/img.png?width=868&amp;amp;height=332&amp;amp;face=0_0_868_332');&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;iOS Architecture Patterns&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Demystifying MVC, MVP, MVVM and VIPER&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;
&lt;p data-ke-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>Swift</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/30</guid>
      <comments>https://h4njun.tistory.com/entry/Model-View-PresenterMVP#entry30comment</comments>
      <pubDate>Mon, 4 Apr 2022 20:41:57 +0900</pubDate>
    </item>
    <item>
      <title>Model-View-Controller(MVC)</title>
      <link>https://h4njun.tistory.com/entry/Model-View-ControllerMVC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘부터 아키텍처 패턴들에 대해서 하나씩 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 그중에서 아마 가장 처음 접하게 될&lt;b&gt; Model-View-Controller&lt;/b&gt;, 즉 &lt;b&gt;MVC&lt;/b&gt;에 대해서 살펴보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. &amp;nbsp;MVC&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648966466111&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;Model-View-Controller&quot; data-og-description=&quot;Retired Document Important: This document may not represent best practices for current development. Links to downloads and other resources may no longer be valid. Model-View-Controller The Model-View-Controller (MVC) design pattern assigns objects in an ap&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&quot; data-og-url=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cnsmWy/hyNUuqXyLi/3qn3rM0ykJIokw9OgEgKBk/img.png?width=1304&amp;amp;height=539&amp;amp;face=0_0_1304_539&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cnsmWy/hyNUuqXyLi/3qn3rM0ykJIokw9OgEgKBk/img.png?width=1304&amp;amp;height=539&amp;amp;face=0_0_1304_539');&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;Model-View-Controller&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Retired Document Important: This document may not represent best practices for current development. Links to downloads and other resources may no longer be valid. Model-View-Controller The Model-View-Controller (MVC) design pattern assigns objects in an ap&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;MVC는 앱을&amp;nbsp;&lt;b&gt;Model, View, Controller&lt;/b&gt; 세개의 역할로 분리한 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgP2D/btrymizBCcU/6Nzox9lrNMqxvcHZoG325K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgP2D/btrymizBCcU/6Nzox9lrNMqxvcHZoG325K/img.png&quot; data-alt=&quot;Apple의 MVC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgP2D/btrymizBCcU/6Nzox9lrNMqxvcHZoG325K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgP2D%2FbtrymizBCcU%2F6Nzox9lrNMqxvcHZoG325K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;529&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Apple의 MVC&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;애플의 주장으로는 MVC를 사용하면&lt;/p&gt;
&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;background-color: #ffffff; color: #000000;&quot;&gt;- more reusable(재사용성이 좋다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;- interface&amp;nbsp;be&amp;nbsp;better&amp;nbsp;defined(인터페이스 정의가 더 쉽다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;- &lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;more easily extensible(확장성도 좋다)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;- &lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;many Cocoa technologies and architectures are based on MVC(심지어 Cocoa Framework도 MVC를 기준으로 한다)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000; background-color: #ffffff;&quot;&gt;이러한 장점이 있다고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000; background-color: #ffffff;&quot;&gt;정말 어마어마한 아키텍처 패턴이네요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000; background-color: #ffffff;&quot;&gt;그럼 그 요소를 하나씩 알아보도록 하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Model&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC에서의 모델은 다른 아키텍처에서 볼 수 있는 Entity와는 사뭇 느낌이 다릅니다.&lt;/p&gt;
&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; 모두 Model에 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model을 작성할 때 주의할 점은 Model은 여러 화면에서 재사용이 가능한 구조여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는&lt;b&gt; View나 Controller에 의존적이어서는 안 되겠죠.&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;그렇다면 Model은 Controller에게 어떻게 변화된 정보를 알릴 수 있을까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlWXoN/btrye4p43hz/oIQQCbcaANKGL3vbpCVnc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlWXoN/btrye4p43hz/oIQQCbcaANKGL3vbpCVnc0/img.png&quot; data-alt=&quot;어떻게 알릴건디..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlWXoN/btrye4p43hz/oIQQCbcaANKGL3vbpCVnc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlWXoN%2Fbtrye4p43hz%2FoIQQCbcaANKGL3vbpCVnc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;275&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;375&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;여러 가지 방법이 있겠지만 NotificationCenter를 이용하는 방법과 Key-Value-Observing과 같은 &lt;b&gt;옵저버 패턴&lt;/b&gt;을 사용해 해결하는 것이 대표적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model이 상태가 변화하면 Model은 상태가 변했다는 사실을 관찰자들에게 알리는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model은 직접적으로 그 관찰자가 누군지는 모르지만 관찰자들은 Model의 정보가 바뀐 것을 알 수 있습니다.&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;3. View&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서 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;b&gt;상호작용하는 영역&lt;/b&gt;을 담당하는 층입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIView를 상속받는 객체들은 대부분 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;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;b&gt;Controller를 직접 알고 있어서는 안 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpAvED/btryiGhhdkA/3Wr5kZzwqoFhubLsyj4N91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpAvED/btryiGhhdkA/3Wr5kZzwqoFhubLsyj4N91/img.png&quot; data-alt=&quot;그럼 넌 또 어떻게 전달할건디;;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpAvED/btryiGhhdkA/3Wr5kZzwqoFhubLsyj4N91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpAvED%2FbtryiGhhdkA%2F3Wr5kZzwqoFhubLsyj4N91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;367&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;367&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;이러한 문제는 Target-Action이나 delegate, datasource와 같은 &lt;b&gt;위임자 패턴&lt;/b&gt;을 통해 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TableView나 CollectionView를 다뤄보신 분들이라면 익숙하실 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View는 어떤 이벤트가 발생하면 위임자에게 넘기겠다는 사실만 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Controller&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller는 &lt;b&gt;뷰와 모델 사이의 중개자 역할&lt;/b&gt;을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View로부터 이벤트를 전달받아 해당 이벤트를 자신이 직접 처리해 View에게 다시 알리거나 Model의 변화가 필요한 경우 Model에게 알립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model의 변화가 발생하면 변화를 감지해 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;따라서 Controller는 View와 Model을 모두 알고 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 MVC에 개념을 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 앱을 통해 직접 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sdYR3/btrydzXrRLk/oBnXIpl84TkqgjccKNIXy0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sdYR3/btrydzXrRLk/oBnXIpl84TkqgjccKNIXy0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sdYR3/btrydzXrRLk/oBnXIpl84TkqgjccKNIXy0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/sdYR3/btrydzXrRLk/oBnXIpl84TkqgjccKNIXy0/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;238&quot; height=&quot;515&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+를 누르면 값이 숫자가 증가하고 -를 누르면 값이 감소하는 앱입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 Model은 저 Label이 표시하기 위한 값이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648975079219&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Model

class Amount: NSObject {
    @objc dynamic var number: Int
    
    override init() {
        self.number = 0
    }
    
    func increase() {
        self.number += 1
    }
    
    func decrease() {
        self.number -= 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View는 Label, + 버튼, - 버튼입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648975165978&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewController: UIViewController { // Controller
    
    private var amount: Amount?
    
    // View
    private lazy var increaseButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: &quot;plus&quot;), for: .normal)
        // + 버튼 눌렀을 경우 호출될 메서드 지정
        button.addTarget(self, action: #selector(increaseButtonTouched(_sender:)), for: .touchUpInside)
        return button
    }()
    
    private lazy var decreaseButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: &quot;minus&quot;), for: .normal)
        // - 버튼 눌렀을 경우 호출될 메서드 지정
        button.addTarget(self, action: #selector(decreaseButtonTouched(_sender:)), for: .touchUpInside)
        return button
    }()
    
    private lazy var amountLabel: UILabel = {
        let label = UILabel()
        label.font = label.font.withSize(32)
        label.text = String(0)
        return label
    }()
    
    private var observation: NSKeyValueObservation?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.amount = Amount()
        //모델의 값 변화 관찰
        self.observation = self.amount?.observe(\.number, options: .new) {object, change in
            self.amountLabel.text = String(change.newValue ?? 0)
        }
    }
    
    @objc func increaseButtonTouched(_sender: UIButton) {
        self.amount?.increase()
    }
    
    @objc func decreaseButtonTouched(_sender: UIButton) {
        self.amount?.decrease()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서 Button들에 addTarget을 통해 호출될 메서드를 지정하고&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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플의 말대로 MVC는 훌륭한 아키텍처임에는 틀림이 없습니다.&lt;/p&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와 Model의 재사용성이 강해지고 이는 결국 유지보수에 용이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 UIKit 관련 객체들이 MVC를 기반으로 작성되었기 때문에 아키텍처를 구성함에도 어렵지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;빠른 개발&lt;/b&gt;이 필요한 경우 적합한 아키텍처 패턴이라 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단점도 역시 존재합니다.&lt;/p&gt;
&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;Controller의 역할이 너무나도 많다는 점&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서도 살펴보았듯이 Controller는 View의 위임자 역할도 해야 하고, Model의 관찰자 역할도 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한 개의 객체가 너무 많은 역할을 하면 좋지 않다는 점은 Solid원칙의 단일 책임 원칙을 통해서 알 수 있습니다.&lt;/p&gt;
&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;Controller의 테스트 코드 작성이 어렵습니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller의 작성된 코드가 굉장히 많음에도 불구하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIViewController의 Life Cycle과 관련된 코드들은 직접 앱을 실행시키지 않는 한 테스트가 어렵습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 MVC에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에는 또 다른 아키텍처들을 통해 과연 MVC의 문제점을 어떻게 해결했을지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Swift</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/29</guid>
      <comments>https://h4njun.tistory.com/entry/Model-View-ControllerMVC#entry29comment</comments>
      <pubDate>Sun, 3 Apr 2022 17:55:37 +0900</pubDate>
    </item>
    <item>
      <title>Property Wrapper</title>
      <link>https://h4njun.tistory.com/entry/Property-Wrapper</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 SwiftUI에 대해서 학습을 하고 있는 와중에 @State니 @Binding이니 낯선 키워드들을 접하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 보니 Combine을 사용하면서 @Published와 같은 Property Wrapper들을 사용하고 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 그 Property Wrapper에 대해서 알아보도록 하겠습니다.&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;1. Property Wrapper&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648215066562&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;Properties &amp;mdash; The Swift Programming Language (Swift 5.6)&quot; data-og-description=&quot;Properties Properties associate values with a particular class, structure, or enumeration. Stored properties store constant and variable values as part of an instance, whereas computed properties calculate (rather than store) a value. Computed properties a&quot; data-og-host=&quot;docs.swift.org&quot; data-og-source-url=&quot;https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617&quot; data-og-url=&quot;https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617&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;Properties &amp;mdash; The Swift Programming Language (Swift 5.6)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Properties Properties associate values with a particular class, structure, or enumeration. Stored properties store constant and variable values as part of an instance, whereas computed properties calculate (rather than store) a value. Computed properties a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.swift.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Property Wrapper를 한국말로 생각해보면 속성 포장지(?)라는 뜻을 가지고 있는데요.&lt;/p&gt;
&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;@State의 경우에는 읽고 쓸 수 있는 프로퍼티라고 선언하는 것이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Published 같은 경우는 값을 방출할 수 있는 프로퍼티라고 말하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &amp;nbsp;여러 프로퍼티들에서 같은 getter/setter가 반복해서 사용되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 Property Wrapper를 통해 정의해주게 되면 &lt;b&gt;재사용성이 올라가고 프로퍼티를 정의하는 부분의 가독성이 좋아집니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Property Wrapper를 직접 정의하는 방법은 매우 간답합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 @propertyWrapper 어노테이션을 구조체, 클래스, 열거형 앞에 붙여주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oydnZ/btrxgGDLAqD/Jrl3RpIwLVeenTAl7Kfw10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oydnZ/btrxgGDLAqD/Jrl3RpIwLVeenTAl7Kfw10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oydnZ/btrxgGDLAqD/Jrl3RpIwLVeenTAl7Kfw10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoydnZ%2FbtrxgGDLAqD%2FJrl3RpIwLVeenTAl7Kfw10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;787&quot; height=&quot;43&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 타입을 생성하면 wrappedValue, 즉 포장된 값을 포함하지 않다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TwelveOrLess 타입은 저장하려는 값과 12 중에 더 작은 값을 가지는 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 정의할 수 있겠죠.&lt;/p&gt;
&lt;pre id=&quot;code_1648217303218&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@propertyWrapper
struct TwelveOrLess {
    private var number: Int = 0

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정의한 Property Wrapper는 다른 타입의 프로퍼티 앞에 @TwelveOrLess를 붙여서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648218761220&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Rectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게만 정의하면 문제가 조금 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0LVLv/btrxkhib2nv/LRgDzZw4EPVqtEMywaxxc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0LVLv/btrxkhib2nv/LRgDzZw4EPVqtEMywaxxc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0LVLv/btrxkhib2nv/LRgDzZw4EPVqtEMywaxxc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0LVLv%2Fbtrxkhib2nv%2FLRgDzZw4EPVqtEMywaxxc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;236&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 생성자의 변수의 Int 타입이 아니라 Property Wrapper타입인 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제는 Property Wrapper에 init(wrappedValue: Int)를 추가하므로 해결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648218958349&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@propertyWrapper
struct TwelveOrLess {
    private var number: Int = 0

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }

    init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
    }
}&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;위와 같이 wrappedValue를 매개변수로 하는 생성자는 프로퍼티에 값이 할당된 경우 호출됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NY2LI/btrxhNilkpC/okKGgiZAnM3hk2RvswCqsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NY2LI/btrxhNilkpC/okKGgiZAnM3hk2RvswCqsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NY2LI/btrxhNilkpC/okKGgiZAnM3hk2RvswCqsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNY2LI%2FbtrxhNilkpC%2FokKGgiZAnM3hk2RvswCqsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;432&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위와 같이 구조체에서 height와 width에 초기값을 주었을 때도 init(wrappedValue:)가 호출되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 초기값을 부여하지 않고 생성할 때도 의도한 대로 Int형을 이용해 인스턴스를 생성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oD5Rq/btrxkfknqeM/shzt5C38vi3dFiblek9Ad0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oD5Rq/btrxkfknqeM/shzt5C38vi3dFiblek9Ad0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oD5Rq/btrxkfknqeM/shzt5C38vi3dFiblek9Ad0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoD5Rq%2FbtrxkfknqeM%2Fshzt5C38vi3dFiblek9Ad0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;201&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Property Wrapper는 추가적인 기능을 제공할 수 있는 &lt;b&gt;projectedValue&lt;/b&gt;라는 프로퍼티를 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서는 값의 변화 여부를 판단하는 데 사용하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648219912270&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@propertyWrapper
struct TwelveOrLess {
    private var number: Int = 0
    private(set) var projectedValue: Bool = false

    var wrappedValue: Int {
        get { return number }
        set {
            if newValue &amp;gt; 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }

    init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Bool 타입의 projectedValue를 선언하고 12보다 값이 커서 값이 변경된 경우에는 projectedValue를 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;projectedValue는 외부에서 해당 프로퍼티의 앞에 &lt;b&gt;$&lt;/b&gt;를 붙여서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LMxwX/btrxeIBww4P/EHOJ0lTw6TVCBACkwBLuy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LMxwX/btrxeIBww4P/EHOJ0lTw6TVCBACkwBLuy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LMxwX/btrxeIBww4P/EHOJ0lTw6TVCBACkwBLuy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLMxwX%2FbtrxeIBww4P%2FEHOJ0lTw6TVCBACkwBLuy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;109&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 Bool 타입이 아니라 다른 타입으로도 선언이 가능하니 상황에 맞게 사용하면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-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>Swift</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/28</guid>
      <comments>https://h4njun.tistory.com/entry/Property-Wrapper#entry28comment</comments>
      <pubDate>Fri, 25 Mar 2022 23:55:50 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI - Stack</title>
      <link>https://h4njun.tistory.com/entry/SwiftUI-Stack</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 SwiftUI에서 View들을 배치하는 데 사용되는 Stack에 관해서 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 세 종류의 Stack을 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dN7LeO/btrxdPsjirg/PwBGrRVUrnpRW3ZRUp0wt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dN7LeO/btrxdPsjirg/PwBGrRVUrnpRW3ZRUp0wt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dN7LeO/btrxdPsjirg/PwBGrRVUrnpRW3ZRUp0wt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdN7LeO%2FbtrxdPsjirg%2FPwBGrRVUrnpRW3ZRUp0wt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;593&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수직으로 뷰들을 배치하는 VStack, 수평으로 뷰들을 배치하는 HStack, 그리고 Z축으로 뷰들을 배치하는 ZStack이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(LazyVStack과 LazyHStack은 나중에 다루도록 하겠습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit에서 StackView를 다루어봤던 분들이라면 어떤 식으로 뷰들이 배치가 되는지 상상이 되실거라 생각합니다.&lt;/p&gt;
&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. VStack&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/vstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/swiftui/vstack&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648129640770&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/swiftui/vstack&quot; data-og-url=&quot;https://developer.apple.com/documentation/swiftui/vstack&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/vstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/vstack&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sLzJL/btrxaPG0iOL/Y55ftuyMkaWbABZMqWJ5rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sLzJL/btrxaPG0iOL/Y55ftuyMkaWbABZMqWJ5rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sLzJL/btrxaPG0iOL/Y55ftuyMkaWbABZMqWJ5rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsLzJL%2FbtrxaPG0iOL%2FY55ftuyMkaWbABZMqWJ5rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;202&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 뷰들을 &lt;b&gt;수직&lt;/b&gt;으로 배치하는 뷰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648129835110&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    var body: some View {
        HStack{
            Text(&quot;One&quot;)
            Text(&quot;Two&quot;)
            Text(&quot;Three&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 세 개의 Text를 선언하면 추가해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/76HEs/btrxfv05cHg/dw0GzoxbA4AvMidSF36C8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/76HEs/btrxfv05cHg/dw0GzoxbA4AvMidSF36C8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/76HEs/btrxfv05cHg/dw0GzoxbA4AvMidSF36C8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F76HEs%2Fbtrxfv05cHg%2Fdw0GzoxbA4AvMidSF36C8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;722&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세개의 Text가 수직으로 배치된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. HStack&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/hstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/swiftui/hstack&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648130303931&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/swiftui/hstack&quot; data-og-url=&quot;https://developer.apple.com/documentation/swiftui/hstack&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/hstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/hstack&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buUFBG/btrxcgX3jxx/B2SbH6KnYph8bhIbzYDxzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buUFBG/btrxcgX3jxx/B2SbH6KnYph8bhIbzYDxzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buUFBG/btrxcgX3jxx/B2SbH6KnYph8bhIbzYDxzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuUFBG%2FbtrxcgX3jxx%2FB2SbH6KnYph8bhIbzYDxzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;193&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 뷰들을 수평으로 배치하는 뷰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648130493920&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    var body: some View {
        HStack{
            Text(&quot;One&quot;)
            Text(&quot;Two&quot;)
            Text(&quot;Three&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VStack을 HStack으로 바꾼 결과를 확인해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYtMsa/btrw71AzW03/2DnjDjGCzQFiVbaGKuWI1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYtMsa/btrw71AzW03/2DnjDjGCzQFiVbaGKuWI1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYtMsa/btrw71AzW03/2DnjDjGCzQFiVbaGKuWI1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYtMsa%2Fbtrw71AzW03%2F2DnjDjGCzQFiVbaGKuWI1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;721&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 수평하게 뷰들을 배치한 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. ZStack&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/zstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/swiftui/zstack&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648130759876&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/swiftui/zstack&quot; data-og-url=&quot;https://developer.apple.com/documentation/swiftui/zstack&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/zstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/zstack&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfd3DW/btrxeyX36uW/v0rmXWkPbcgZRESKTQG4lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfd3DW/btrxeyX36uW/v0rmXWkPbcgZRESKTQG4lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfd3DW/btrxeyX36uW/v0rmXWkPbcgZRESKTQG4lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfd3DW%2FbtrxeyX36uW%2Fv0rmXWkPbcgZRESKTQG4lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;198&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 뷰들을 Z축으로 오버레이 하는 뷰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648131139130&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    var body: some View {
        ZStack{
            Text(&quot;One&quot;)
            Text(&quot;Two&quot;)
            Text(&quot;Three&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 ZStack으로 변경 후 실행해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAcd2a/btrxc3K1Q84/JLJJ8TistxCzI73bYkgNUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAcd2a/btrxc3K1Q84/JLJJ8TistxCzI73bYkgNUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAcd2a/btrxc3K1Q84/JLJJ8TistxCzI73bYkgNUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAcd2a%2Fbtrxc3K1Q84%2FJLJJ8TistxCzI73bYkgNUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;707&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 느낌인지 아시겠나요??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 쉽게 이해하기 위해 도형을 통해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648131387224&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    var body: some View {
        ZStack{
            Color.red
                .frame(width: 100, height: 100)
                .offset(x: 0, y: 0)
            Color.green
                .frame(width: 100, height: 100)
                .offset(x: 10, y: 10)
            Color.blue
                .frame(width: 100, height: 100)
                .offset(x: 20, y: 20)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100 * 100 크기의 빨강, 초록, 파랑 뷰를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3bFpl/btrxbOHRkdH/MOga0ytBvFbRC7IQvXzTm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3bFpl/btrxbOHRkdH/MOga0ytBvFbRC7IQvXzTm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3bFpl/btrxbOHRkdH/MOga0ytBvFbRC7IQvXzTm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3bFpl%2FbtrxbOHRkdH%2FMOga0ytBvFbRC7IQvXzTm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;343&quot; height=&quot;709&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZStack은 나중에 추가된 뷰일 수록 더 높은 Z좌표를 부여합니다.&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;style6&quot; /&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을 생성할 때 alignment 옵션을 통해 정렬 방향을 정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW3wAW/btrxcxMmCky/SEK5xIHoiMKzjnkXBidkyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW3wAW/btrxcxMmCky/SEK5xIHoiMKzjnkXBidkyK/img.png&quot; data-alt=&quot;HStack, ZStack도 정렬 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW3wAW/btrxcxMmCky/SEK5xIHoiMKzjnkXBidkyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW3wAW%2FbtrxcxMmCky%2FSEK5xIHoiMKzjnkXBidkyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;276&quot; height=&quot;122&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HStack, ZStack도 정렬 가능&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;VStack을 leading 정렬한 결과입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYBzsC/btrxahKuZT9/jLAGM17dXcoVkX57dVxCa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYBzsC/btrxahKuZT9/jLAGM17dXcoVkX57dVxCa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYBzsC/btrxahKuZT9/jLAGM17dXcoVkX57dVxCa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYBzsC%2FbtrxahKuZT9%2FjLAGM17dXcoVkX57dVxCa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;337&quot; height=&quot;716&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 VStack과 HStack의 경우에는 뷰들 사이의 공간을 결정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s15bA/btrxb9LMmrG/5QQdtaSQpB4cakclLgKht0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s15bA/btrxb9LMmrG/5QQdtaSQpB4cakclLgKht0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s15bA/btrxb9LMmrG/5QQdtaSQpB4cakclLgKht0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs15bA%2Fbtrxb9LMmrG%2F5QQdtaSQpB4cakclLgKht0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;178&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGeMsR/btrxaPttnFL/KsQIMhCRsSKtiPTvMtH0yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGeMsR/btrxaPttnFL/KsQIMhCRsSKtiPTvMtH0yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGeMsR/btrxaPttnFL/KsQIMhCRsSKtiPTvMtH0yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGeMsR%2FbtrxaPttnFL%2FKsQIMhCRsSKtiPTvMtH0yK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;359&quot; height=&quot;718&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spacing을 100으로 설정한 결과입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ref.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648129228932&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/swiftui/building-layouts-with-stack-views&quot; data-og-url=&quot;https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views&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>iOS</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/27</guid>
      <comments>https://h4njun.tistory.com/entry/SwiftUI-Stack#entry27comment</comments>
      <pubDate>Thu, 24 Mar 2022 23:31:14 +0900</pubDate>
    </item>
    <item>
      <title>동시성 프로그래밍(8) OperationQueue</title>
      <link>https://h4njun.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D8-OperationQueue</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 OperationQueue에 대해서 알아보도록 하겠습니다.&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;1. OperationQueue&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/operationqueue&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647591865154&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/operationqueue&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간까지 Operation에 대해서 알아보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OperationQueue는 이름 그대로 Operation들의 실행을 제어하는 Queue입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchQueue를 생각하면서 OperationQueue는 어떤 속성들을 가지고 있는지 하나씩 확인해보겠습니다.&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) Serial or Concurrent?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1414982-maxconcurrentoperationcount&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/operationqueue/1414982-maxconcurrentoperationcount&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647858509893&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/operationqueue/1414982-maxconcurrentoperationcount&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1414982-maxconcurrentoperationcount&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1414982-maxconcurrentoperationcount&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1414982-maxconcurrentoperationcount&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;OperationQueue는 &lt;b&gt;maxConcurrentOperationCount&lt;/b&gt;라는 프로퍼티를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 진행할 작업의 수를 결정하는 프로퍼티입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0J8a7/btrwJxeDHNo/ypkE8qkTkDZXCgEH8Mo9kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0J8a7/btrwJxeDHNo/ypkE8qkTkDZXCgEH8Mo9kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0J8a7/btrwJxeDHNo/ypkE8qkTkDZXCgEH8Mo9kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0J8a7%2FbtrwJxeDHNo%2FypkE8qkTkDZXCgEH8Mo9kK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;91&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OperationQueue를 생성하고 maxConcurrentOperationCount의 기본값을 확인하면 -1인 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값이 -1일 경우 시스템이 알아서 현재 상태에 따라 동적으로 동시 진행할 작업의 수를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 값을 1로 바꾼다면 Serial한 Queue를 만들 수 있겠죠?&lt;/p&gt;
&lt;pre id=&quot;code_1647850883704&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HJOperation: Operation {
    var number: Int
    
    init(number: Int) {
        self.number = number
    }

    override func main() {
        print(&quot;작업\(self.number) 시작&quot;)
        sleep(3)
        print(&quot;작업\(self.number) 완료&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Operation 클래스를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647851447775&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let op1 = HJOperation(number: 1)
let op2 = HJOperation(number: 2)
let op3 = HJOperation(number: 3)

let queue = OperationQueue()

queue.addOperation(op1)
queue.addOperation(op2)
queue.addOperation(op3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation 3개를 생성해 Queue에 추가해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;78&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctCmET/btrwwrmLYbl/3s7Bph9ikeR4W2Kd97vFg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctCmET/btrwwrmLYbl/3s7Bph9ikeR4W2Kd97vFg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctCmET/btrwwrmLYbl/3s7Bph9ikeR4W2Kd97vFg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctCmET%2FbtrwwrmLYbl%2F3s7Bph9ikeR4W2Kd97vFg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;78&quot; height=&quot;108&quot; data-origin-width=&quot;78&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 동시에 진행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 maxConcurrentOperationCount를 1로 설정한다면&lt;/p&gt;
&lt;pre id=&quot;code_1647851565637&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let op1 = HJOperation(number: 1)
let op2 = HJOperation(number: 2)
let op3 = HJOperation(number: 3)

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1

queue.addOperation(op1)
queue.addOperation(op2)
queue.addOperation(op3)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;74&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pSIb9/btrwJW6llNZ/6J25UHhJenoex0204fdzHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pSIb9/btrwJW6llNZ/6J25UHhJenoex0204fdzHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pSIb9/btrwJW6llNZ/6J25UHhJenoex0204fdzHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpSIb9%2FbtrwJW6llNZ%2F6J25UHhJenoex0204fdzHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;74&quot; height=&quot;110&quot; data-origin-width=&quot;74&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 하나씩만 Serial하게 실행됩니다.&lt;/p&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;2) Quality of Service&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1417919-qualityofservice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/operationqueue/1417919-qualityofservice&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647858487114&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/operationqueue/1417919-qualityofservice&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1417919-qualityofservice&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1417919-qualityofservice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1417919-qualityofservice&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;OperationQueue도 DisaptchQueue와 마찬가지로 &lt;b&gt;qualityOfService&lt;/b&gt; 프로퍼티를 이용하여 QoS를 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OperationQueue의 QoS는 DispatchQueue의 6종류에서 .unspecified를 제외한 5개로 이루어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NGPBm/btrwzda0M5G/ah3cdIrDqXrFVGmKFklOg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NGPBm/btrwzda0M5G/ah3cdIrDqXrFVGmKFklOg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NGPBm/btrwzda0M5G/ah3cdIrDqXrFVGmKFklOg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNGPBm%2Fbtrwzda0M5G%2Fah3cdIrDqXrFVGmKFklOg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;157&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 userInteractive &amp;gt; userInitiated &amp;gt; default &amp;gt; utility &amp;gt; background 순입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 생성한 OperationQueue의 경우 기본값은 background입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OperationQueue 또한 main 메서드를 통해 main Queue를 제공하는데요. 메인 큐에 경우에는 userInteractive가 기본값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647859300906&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let userInteractiveQueue = OperationQueue()
let backgroundQueue = OperationQueue()

userInteractiveQueue.qualityOfService = .userInteractive
backgroundQueue.qualityOfService = .background

backgroundQueue.addOperation(op1)
userInteractiveQueue.addOperation(op2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QoS가 다른 두 개의 Queue를 생성하고 작업을 추가하면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;73&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDSETk/btrwIhDsTsy/ji9OJM6yMWyU4LJauENHHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDSETk/btrwIhDsTsy/ji9OJM6yMWyU4LJauENHHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDSETk/btrwIhDsTsy/ji9OJM6yMWyU4LJauENHHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDSETk%2FbtrwIhDsTsy%2Fji9OJM6yMWyU4LJauENHHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;73&quot; height=&quot;70&quot; data-origin-width=&quot;73&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QoS가 높은 Queue의 작업이 먼저 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;operationQueue뿐만이 아니라 각각의 Operation에도 QoS를 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Operation의 QoS가 명시되어 있을 경우 해당 QoS를 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation의 QoS는 기본값이 default입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1647873448336&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;op1.qualityOfService = .userInitiated // op2의 QoS를 userInitiated로 증가

let defaultQueue = OperationQueue()
let backgroundQueue = OperationQueue()

defaultQueue.qualityOfService = .default
backgroundQueue.qualityOfService = .background

backgroundQueue.addOperation(op1)
defaultQueue.addOperation(op2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;op1의 QoS를 userInitiated로 증가시키면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;70&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zff44/btrwA5RyLSy/0Xb3j7NpYKuHkjpQYHAHpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zff44/btrwA5RyLSy/0Xb3j7NpYKuHkjpQYHAHpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zff44/btrwA5RyLSy/0Xb3j7NpYKuHkjpQYHAHpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZff44%2FbtrwA5RyLSy%2F0Xb3j7NpYKuHkjpQYHAHpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;70&quot; height=&quot;75&quot; data-origin-width=&quot;70&quot; data-origin-height=&quot;75&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;backgroundQueue에 추가되었음에도 불구하고 op1이 먼저 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 OperationQueue는 작업을 &amp;nbsp;호출할 DispatchQueue를 지정할 수 underlyingQueue 프로퍼티가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 underlyingQueue가 명시된다면 최우선적으로 해당 Queue의 QoS를 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647874443798&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;op1.qualityOfService = .userInitiated // op1의 QoS를 userInitiated를 증가

let defaultQueue = OperationQueue()
let backgroundQueue = OperationQueue()

defaultQueue.underlyingQueue = DispatchQueue.global(qos: .userInteractive)
// global userInteractiveQueue를 defaultQueue의 underlyingQueue로 지정

defaultQueue.qualityOfService = .default
backgroundQueue.qualityOfService = .background

backgroundQueue.addOperation(op1)
defaultQueue.addOperation(op2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 default QoS를 가지고 있던 Queue의 underlyingQueue를 userInteractiveQueue로 설정하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;79&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bORcu3/btrwA541wZX/jUGq35fdTjdITVLofWTwX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bORcu3/btrwA541wZX/jUGq35fdTjdITVLofWTwX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bORcu3/btrwA541wZX/jUGq35fdTjdITVLofWTwX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbORcu3%2FbtrwA541wZX%2FjUGq35fdTjdITVLofWTwX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;79&quot; height=&quot;76&quot; data-origin-width=&quot;79&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 op2가 먼저 시작되는 것을 확인할 수 있습니다.&lt;/p&gt;
&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;underlyingQueue의 QoS &amp;gt; Operation의 QoS &amp;gt; OperationQueue의 QoS&lt;/b&gt; 순으로 QoS가 정해진다고 볼 수 있습니다.&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) Operation 추가 방법&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2nJUA/btrwn4ZAn1W/KP51HMuacKBP1ukY3pG261/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2nJUA/btrwn4ZAn1W/KP51HMuacKBP1ukY3pG261/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2nJUA/btrwn4ZAn1W/KP51HMuacKBP1ukY3pG261/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2nJUA%2Fbtrwn4ZAn1W%2FKP51HMuacKBP1ukY3pG261%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;774&quot; height=&quot;315&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 3가지 방법으로 Operation을 추가할 수 있습니다.&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;b&gt;func addOperation(Operation)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;앞서 했던 방식대로 Opertaion을 생성해 추가하는 방식입니다.&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;b&gt;func addOperation([Operation], waitUntilFinished: Bool)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Operation들의 배열을 추가하는 방식입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; waitUntilFinished를 true로 설정했을 시 Operation들이 모두 완료될 때까지 현재 스레드를 차단합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 당연히 main 스레드에서의 사용은 주의해야겠습니다.&lt;/span&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;b&gt;func addOperation(() -&amp;gt; Void)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;DispatchQueue와 마찬가지로 클로저를 그대로 추가하는 것도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation이 OpertaionQueue에 추가되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start() 메서드를 호출할 필요 없이 스레드에 배정되면 isExecuting 프로퍼티가 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;Opertaion의 isFinished 프로퍼티가 true가 되면(완료되거나, 취소되거나)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpertaionQueue를 떠납니다.&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;4)&amp;nbsp;waitUntilAllOperationsAreFinished&lt;span&gt;()&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;OpertaionQueue의 존재하는 Operation이 모두 완료될 때까지 현재 스레드를 차단하는 메서드입니다.&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;단 현재 스레드에서는 OperationQueue에 작업 추가가 당연히 불가능하지만 다른 스레드에서는 추가가 가능합니다.&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_1647876728251&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;backgroundQueue.addOperation(op1)
backgroundQueue.addOperation(op2)

backgroundQueue.waitUntilAllOperationsAreFinished()
print(&quot;complete&quot;)

backgroundQueue.addOperation(op3)
//op3는 나중에 추가된다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;addOpertaion(op3)의 경우는 op1, op2가 마무리되어야 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;77&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK2S68/btrwMHA6mv0/9guYlcEWmSzVeY3wgDncM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK2S68/btrwMHA6mv0/9guYlcEWmSzVeY3wgDncM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK2S68/btrwMHA6mv0/9guYlcEWmSzVeY3wgDncM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK2S68%2FbtrwMHA6mv0%2F9guYlcEWmSzVeY3wgDncM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;77&quot; height=&quot;123&quot; data-origin-width=&quot;77&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp;waitUntilAllOperationsAreFinished&lt;span&gt;()를 호출한 스레드와 다른 스레드에서 작업을 추가한다면&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647876989677&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;backgroundQueue.addOperation(op1)
backgroundQueue.addOperation(op2)

DispatchQueue.global().async {
    backgroundQueue.waitUntilAllOperationsAreFinished()
    print(&quot;complete&quot;)
}

backgroundQueue.addOperation(op3)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;81&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs4DPw/btrwJWyWdyF/oBxlSVg8UdhD7NkaaN3K51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs4DPw/btrwJWyWdyF/oBxlSVg8UdhD7NkaaN3K51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs4DPw/btrwJWyWdyF/oBxlSVg8UdhD7NkaaN3K51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs4DPw%2FbtrwJWyWdyF%2FoBxlSVg8UdhD7NkaaN3K51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;81&quot; height=&quot;123&quot; data-origin-width=&quot;81&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 추가된 작업까지 완료되어야 현재 스레드 차단을 해제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5) 일시중지 - 재개&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1415909-issuspended&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/operationqueue/1415909-issuspended&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647877326445&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/operationqueue/1415909-issuspended&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1415909-issuspended&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1415909-issuspended&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/operationqueue/1415909-issuspended&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;OperationQueue는 &lt;b&gt;isSuspended&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;이 값을 true로 변경한다면 현재 실행 중인 작업은 일단 계속 실행하지만 새로운 작업을 실행하지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서도 Queue에 작업 추가는 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 속성을 false로 변경한다면 작업 재개가 가능합니다. 기본값은 false입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 OperationQueue에 대해서 알아보았습니다.&lt;/p&gt;
&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;style6&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;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot;&gt;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648129472507&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;iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션&quot; data-og-description=&quot;동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., - 강의 소개 | 인프런...&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbuUxP/hyNH8CEJbc/Q0UkNll0pr8STK85jCHFp1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/nOLjR/hyNHZlp26o/bzKdIEswkxij6RQ3kUnLk0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; data-source-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbuUxP/hyNH8CEJbc/Q0UkNll0pr8STK85jCHFp1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/nOLjR/hyNHZlp26o/bzKdIEswkxij6RQ3kUnLk0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., - 강의 소개 | 인프런...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>iOS</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/26</guid>
      <comments>https://h4njun.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D8-OperationQueue#entry26comment</comments>
      <pubDate>Tue, 22 Mar 2022 00:46:29 +0900</pubDate>
    </item>
    <item>
      <title>UIResonder &amp;amp; Responder Chain</title>
      <link>https://h4njun.tistory.com/entry/UIResonder-Responder-Chain</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 UIResponder와 Responder Chain에 대해서 알아보겠습니다.&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;1. UIResponder&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiresponder/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/uiresponder/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647501208993&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/uiresponder/&quot; data-og-url=&quot;https://developer.apple.com/documentation/uikit/uiresponder/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiresponder/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/uikit/uiresponder/&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;Responder Chain을 이해하기 위해서는 먼저 UIResponder에 대해서 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIResponder는 이벤트에 반응하고 처리하기 위한 추상 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit이 제공하는 대부분의 객체들이 UIResponder를 상속받고 있습니다.&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;1176&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tzd66/btrwaFyYmr7/M3SIggc5GIB4lJvBSVHaW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tzd66/btrwaFyYmr7/M3SIggc5GIB4lJvBSVHaW0/img.png&quot; data-alt=&quot;UIView, UIViewController 심지어 UIApplication까지 UIResponder를 상속하고 있다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tzd66/btrwaFyYmr7/M3SIggc5GIB4lJvBSVHaW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTzd66%2FbtrwaFyYmr7%2FM3SIggc5GIB4lJvBSVHaW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1176&quot; height=&quot;510&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UIView, UIViewController 심지어 UIApplication까지 UIResponder를 상속하고 있다!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIResponders는 다양한 이벤트들을 처리하기 위한 메서드들을 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 사용해본 &amp;nbsp;touchesBegan(_:with:)과 같은 터치 이벤트들 이외에도 모션 이벤트, 프레스 이벤트, 원격 조종 이벤트들을 위한 메서드들도 제공하고 있습니다.&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;2. Responder Chain&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Responder Chain은 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Responder Chain은 UIResponder들의 연결 리스트라고 생각하면 됩니다.&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;2108&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwhByr/btrwbrULUXY/Gsk2R74UwxiZfDqvhZ98S0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwhByr/btrwbrULUXY/Gsk2R74UwxiZfDqvhZ98S0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwhByr/btrwbrULUXY/Gsk2R74UwxiZfDqvhZ98S0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwhByr%2FbtrwbrULUXY%2FGsk2R74UwxiZfDqvhZ98S0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2108&quot; height=&quot;230&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIResponder는 자신이 처리하지 못한 이벤트들을 다음 UIResponder에게 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNxwxt/btrweylkUH8/7rft0IEGnqTFkoMD7GcJTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNxwxt/btrweylkUH8/7rft0IEGnqTFkoMD7GcJTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNxwxt/btrweylkUH8/7rft0IEGnqTFkoMD7GcJTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNxwxt%2FbtrweylkUH8%2F7rft0IEGnqTFkoMD7GcJTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1764&quot; height=&quot;162&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIResponder는 next라는 프로퍼티를 가지고 있습니다. 자신이 처리하지 못한 이벤트를 이 next Responder에게 보내는 것이죠.&lt;/p&gt;
&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;First Responder&lt;/b&gt;는 이 연결 리스트에서 가장 앞 쪽에 위치한, 이벤트를 가장 먼저 전달받는 UIResponder입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Responder Chain은 뷰 계층에 따라 동적으로 관리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 뷰(z축의 가장 위에 존재하는 뷰)부터 UIApplicationDelegate까지 이벤트를 처리할 수 있는 객체를 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5MKh4/btrweyZ0kDw/fKoykbKMKVPoksEkyXGiDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5MKh4/btrweyZ0kDw/fKoykbKMKVPoksEkyXGiDK/img.png&quot; data-alt=&quot;Responder Chain&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5MKh4/btrweyZ0kDw/fKoykbKMKVPoksEkyXGiDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5MKh4%2FbtrweyZ0kDw%2FfKoykbKMKVPoksEkyXGiDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;395&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Responder Chain&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;(Storyboard에서 임의로 IBAction을 지정한 후 해당 IBAction을 제거한 경우에는 Unrecognized Selector라는 에러를 띄웁니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 많은 분들이 무의식 중에 Responder Chain을 경험해보셨을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 키보드를 내리는 과정을 통해서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OTw30/btrv9P84ErO/4DQ2ayYSlw4nibHylkeZ1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OTw30/btrv9P84ErO/4DQ2ayYSlw4nibHylkeZ1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OTw30/btrv9P84ErO/4DQ2ayYSlw4nibHylkeZ1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOTw30%2Fbtrv9P84ErO%2F4DQ2ayYSlw4nibHylkeZ1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;1168&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 TextField와 회색 뷰를 포함하는 ViewController가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField를 터치하게 되면 키보드가 올라옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-03-17 오후 5.13.44.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVlDO/btrwbq9AAMX/zdlgbuIxYsgwG9Sgt3fPUK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVlDO/btrwbq9AAMX/zdlgbuIxYsgwG9Sgt3fPUK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVlDO/btrwbq9AAMX/zdlgbuIxYsgwG9Sgt3fPUK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kVlDO/btrwbq9AAMX/zdlgbuIxYsgwG9Sgt3fPUK/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;288&quot; height=&quot;1854&quot; data-filename=&quot;화면 기록 2022-03-17 오후 5.13.44.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 화면에 다른 곳을 눌러도 키보드는 내려가지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 키보드를 내려주기 위해서 ViewController에서 다음과 같은 코드를 작성하셨을 겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1647505221602&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func touchesBegan(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        self.textField.resignFirstResponder()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;touchesBegan(_:with:) 메서드를 재정의해서 textField가 더 이상 FirstResponder가 아니게 하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키보드는 Text 입력이 가능한 View가 First Responder여야 화면에 나타나기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 정상적으로 키보드가 내려가게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-03-17 오후 5.28.18.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLzGDj/btrwgC1HsrX/VFuWpLAT58LsUnLMgvBAB0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLzGDj/btrwgC1HsrX/VFuWpLAT58LsUnLMgvBAB0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLzGDj/btrwgC1HsrX/VFuWpLAT58LsUnLMgvBAB0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bLzGDj/btrwgC1HsrX/VFuWpLAT58LsUnLMgvBAB0/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;288&quot; height=&quot;628&quot; data-filename=&quot;화면 기록 2022-03-17 오후 5.28.18.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 회색 뷰를 터치해도 키보드가 내려가는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 ViewController에서 touchesBegan(_:with:)를 재정의했는데 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Responder Chain 때문에 이러한 일이 발생하는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회색 뷰는&amp;nbsp;touchesBegan(_:with:) 메서드가 재정의되어 있지 않기 때문이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 회색 뷰를 Custom View로 만들고 touchesBegan(_:with:) 메서드를 재정의해 아무런 동작도 하지 않게 하거나&lt;/p&gt;
&lt;pre id=&quot;code_1647506545274&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomView: UIView {

    override func touchesBegan(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
        // do nothing
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 next Responder를 nil로 만든다면&lt;/p&gt;
&lt;pre id=&quot;code_1647506585464&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomView: UIView {

    override var next: UIResponder? {
        return nil
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-03-17 오후 5.38.08.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VksCQ/btrwgDl04mE/pl6igtKxZZpkJSdfFYP370/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VksCQ/btrwgDl04mE/pl6igtKxZZpkJSdfFYP370/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VksCQ/btrwgDl04mE/pl6igtKxZZpkJSdfFYP370/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/VksCQ/btrwgDl04mE/pl6igtKxZZpkJSdfFYP370/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;288&quot; height=&quot;628&quot; data-filename=&quot;화면 기록 2022-03-17 오후 5.38.08.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 UIResponder와 Responder Chain에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. UIResponder는 이벤트에 대해서 반응하고 처리하는 객체이고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 이벤트는 FirstResponder부터 처리될 수 있는지 확인하고 Responder Chain을 통해서 처리하지 못한 이벤트를 전달한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/25</guid>
      <comments>https://h4njun.tistory.com/entry/UIResonder-Responder-Chain#entry25comment</comments>
      <pubDate>Thu, 17 Mar 2022 17:58:28 +0900</pubDate>
    </item>
    <item>
      <title>동시성 프로그래밍(7) Operation</title>
      <link>https://h4njun.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D7-Operation</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간까지는 GCD를 이용한 동시성 프로그래밍에 대해서 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터는 동시성 프로그래밍을 위한 또 다른 방법, Operation에 대해서 알아보겠습니다.&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;1. GCD와의 차이&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;GCD를 이용해 동시성 프로그래밍을 할 수 있는데 Operation은 왜 필요할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation도 내부적으로는 GCD를 이용해 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Operation은 GCD가 할 수 없는 조금 더 복잡한 일을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Operation은&amp;nbsp;&lt;b&gt;데이터와 기능을 캡슐화한 클래스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 작업의 재사용성이 향상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 다운로드와 같이 반복되는 작업의 경우 Operation으로 정의해놓으면 Operation을 인스턴스화 하는 것만으로 작업을 생성할 수 있는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Operation의 경우에는 작업의 &lt;b&gt;취소, 순서지정,&amp;nbsp;상태 추적&lt;/b&gt;이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchWorkItem의 경우에는 약하게 지원하던 기능들을 Operation으로 더 강력하게 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Operation부터 알아보도록 하겠습니다.&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;2. Operation&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/operation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647241947290&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/operation&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/operation&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/operation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/operation&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvxCJe/btrv4lS65wu/CqZnzw4FzJKKcCNZuBRF4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvxCJe/btrv4lS65wu/CqZnzw4FzJKKcCNZuBRF4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvxCJe/btrv4lS65wu/CqZnzw4FzJKKcCNZuBRF4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvxCJe%2Fbtrv4lS65wu%2FCqZnzw4FzJKKcCNZuBRF4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;932&quot; height=&quot;134&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation은 하나의 작업에 대하여 관련된 코드와 데이터들을 나타내는 추상 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 클래스이기 때문에 직접 사용하지 않고 Operation을 상속받은 클래스를 생성하는 것으로 원하는 작업은 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation은 Single-Shot 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Single-Shot 객체는 한 번만 사용이 가능한 객체를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation의 경우에는 작업이 완료된 인스턴스를 다시 실행시킬 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation이 제공하는 메서드를 알아보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- start()&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;이 메서드를 호출하게 되면 작업 상태를 업데이트하고 main() 메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 과정은&lt;b&gt; 동기적으로 실행&lt;/b&gt;됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로는 이 메소드를 재정의할 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 작업이 동시적으로 실행되기를 원하는 경우에는 start() 메서드를 재정의해야 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start() 메서드를 재정의한 경우 super 키워드를 호출해서는 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isExecuting, isFinished, isAsynchronous 프로퍼티를 작업의 상태에 따라 변경하여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&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;OperationQueue를 이용하여 작업을 실행하게 될 경우, 기본적으로 비동기적으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start()메소드를 재정의해야 하는 경우가 헷갈려서 찾아본 결과 OperationQueue에 의해서 관리되는 작업이 아닌 작업 스스로 비동기적으로 실행되기를 원할 경우 재정의한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/38546198/nsoperation-start-vs-main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/38546198/nsoperation-start-vs-main&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647326356173&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;NSOperation, start vs main&quot; data-og-description=&quot;According to Apple document on NSOperation, we have to override main method for non-concurrent operations and start method for concurrent operations. But why?&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/38546198/nsoperation-start-vs-main&quot; data-og-url=&quot;https://stackoverflow.com/questions/38546198/nsoperation-start-vs-main&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d8jOim/hyNH1J0R1W/C8kkgsclmKrlToDAnXGi40/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/38546198/nsoperation-start-vs-main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/38546198/nsoperation-start-vs-main&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d8jOim/hyNH1J0R1W/C8kkgsclmKrlToDAnXGi40/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;NSOperation, start vs main&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;According to Apple document on NSOperation, we have to override main method for non-concurrent operations and start method for concurrent operations. But why?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- main()&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation이 수행하기를 원하는 작업을 main() 메서드를 오버라이딩하여 작성하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 아무런 일도 하지 않습니다. 따라서 super 키워드를 호출할 필요가 없습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- cancel()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel() 메서드는 직접적으로 작업을 멈추는 메서드는 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel() 메서드는 isCancelled 프로퍼티를 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;진행 중인 작업을 중단하고 싶은 경우에는 재정의한 main() 메서드 또는 start() 메서드에 isCancelled 프로퍼티를 확인하는 구문을 넣어야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Operation은 작업의 상태 추적이 가능하기 때문에 상태를 나타내는 프로퍼티들을 가지고 있습니다.&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;- isReady&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;- isExecuting&amp;nbsp;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;- isCancelled&lt;br /&gt;&lt;/b&gt;&lt;b&gt;- isFinished&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만 들어도 작업이 어떤 상태인지 알 수 있을 것 같습니다.&lt;/p&gt;
&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_1647342186557&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HJOperation: Operation {
    
    override func main() {
        sleep(3)
        print(&quot;main&quot;, &quot;isReady:&quot;,isReady, &quot;,isExecuting:&quot;, isExecuting,&quot;,isCancelled:&quot;, isCancelled,&quot;,isFinished:&quot;, isFinished)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main() 메서드에 상태를 나타내는 프로퍼티들을 출력하는 코드를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 생성 후 start()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647342371123&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let op = HJOperation()
print(&quot;생성 후&quot;, &quot;isReady:&quot;,op.isReady, &quot;,isExecuting:&quot;, op.isExecuting,&quot;,isCancelled:&quot;, op.isCancelled,&quot;,isFinished:&quot;, op.isFinished)
op.start()
print(&quot;after start()&quot;, &quot;isReady:&quot;,op.isReady, &quot;,isExecuting:&quot;, op.isExecuting,&quot;,isCancelled:&quot;, op.isCancelled,&quot;,isFinished:&quot;, op.isFinished)&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;인스턴스를 생성하고 start() 메서드를 통해 작업을 실행해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEIvfE/btrv4Klva1p/E4gwZekdMSpfAT578SXL8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEIvfE/btrv4Klva1p/E4gwZekdMSpfAT578SXL8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEIvfE/btrv4Klva1p/E4gwZekdMSpfAT578SXL8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEIvfE%2Fbtrv4Klva1p%2FE4gwZekdMSpfAT578SXL8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;50&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스가 생성되면 isReady 프로퍼티만 true인 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start() 메서드를 호출하면 isExecuting 프로퍼티가 true가 되고 main()메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 완료되면 isExecuting 프로퍼티는 false가 되고 isFinished 프로퍼티가 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;b&gt;2. 생성 후 start() 호출하지 않고 cancel()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647342703051&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let op2 = HJOperation()
op2.cancel()
print(&quot;after cancel()&quot;, &quot;isReady:&quot;,op2.isReady, &quot;,isExecuting:&quot;, op2.isExecuting,&quot;,isCancelled:&quot;, op2.isCancelled,&quot;,isFinished:&quot;, op2.isFinished)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스를 생성하고 작업을 실행하지 않고 바로 cancel() 메소드를 호출해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;15&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccuNGO/btrv5nwMG2Q/DFi6NSsNgSTOQSBB3fl6k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccuNGO/btrv5nwMG2Q/DFi6NSsNgSTOQSBB3fl6k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccuNGO/btrv5nwMG2Q/DFi6NSsNgSTOQSBB3fl6k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccuNGO%2Fbtrv5nwMG2Q%2FDFi6NSsNgSTOQSBB3fl6k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;15&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;15&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isCancelled 프로퍼티가 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;이 상태에서 다시 start() 메소드를 호출하여도 작업을 실행되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. start() 호출 후 진행 중인 작업 cancel()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647342990199&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let op3 = HJOperation()
op3.completionBlock = {
    print(&quot;start() -&amp;gt; cancel()&quot;, &quot;isReady:&quot;,op3.isReady, &quot;,isExecuting:&quot;, op3.isExecuting,&quot;,isCancelled:&quot;, op3.isCancelled,&quot;,isFinished:&quot;, op3.isFinished)
}

let queue = OperationQueue()
queue.addOperation(op3)

sleep(1) // 작업에 실행을 보장하기 위해서 1초 대기
op3.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 Operation에 대해서 cancel() 메서드를 호출시켜보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation의 경우 completionBlock 프로퍼티를 통하여 작업 간의 종속성을 부여할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 start() 메서드의 경우 추가적인 구현이 없을 경우 동기로 실행되기 때문에 실험을 위하여 OperationQueue를 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OperationQueue의 경우 기본적으로는 비동기로 동작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pwODW/btrv3grDXoY/Cy4Sh8JGPYUbnnM0ABCG8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pwODW/btrv3grDXoY/Cy4Sh8JGPYUbnnM0ABCG8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pwODW/btrv3grDXoY/Cy4Sh8JGPYUbnnM0ABCG8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpwODW%2Fbtrv3grDXoY%2FCy4Sh8JGPYUbnnM0ABCG8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;34&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 작업에 경우 중간에 멈추지는 않습니다. 그러나 isCancelled 프로퍼티가 true가 된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel 된 작업이 완료된 경우 isCancelled 프로퍼티와 isFinished 프로퍼티가 모두 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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0Dm3X/btrv3OVSSTr/KwsxJA3uAaXH0ZKbFvzK81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0Dm3X/btrv3OVSSTr/KwsxJA3uAaXH0ZKbFvzK81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0Dm3X/btrv3OVSSTr/KwsxJA3uAaXH0ZKbFvzK81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0Dm3X%2Fbtrv3OVSSTr%2FKwsxJA3uAaXH0ZKbFvzK81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Operation에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에는 Operation들을 관리할 수 있는 OperationQueue에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ref.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647344822988&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;iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션&quot; data-og-description=&quot;동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., - 강의 소개 | 인프런...&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbuUxP/hyNH8CEJbc/Q0UkNll0pr8STK85jCHFp1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/nOLjR/hyNHZlp26o/bzKdIEswkxij6RQ3kUnLk0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbuUxP/hyNH8CEJbc/Q0UkNll0pr8STK85jCHFp1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/nOLjR/hyNHZlp26o/bzKdIEswkxij6RQ3kUnLk0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., - 강의 소개 | 인프런...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/24</guid>
      <comments>https://h4njun.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D7-Operation#entry24comment</comments>
      <pubDate>Tue, 15 Mar 2022 20:49:28 +0900</pubDate>
    </item>
    <item>
      <title>동시성 프로그래밍(6) Race Condition(경쟁 상태) 해결 방법</title>
      <link>https://h4njun.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D6-Race-Condition%EA%B2%BD%EC%9F%81-%EC%83%81%ED%83%9C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 시간에는 동시성 프로그래밍을 하면서 발생할 수 있는 문제들에 대해서 알아보았는데요.&lt;/p&gt;
&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;Thread-Safe한 코드를 작성하는 방법&lt;/b&gt;을 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TSan(Thread Sanitizer)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode는 Thread Sanitizer라는 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sanitizer라는 단어가 낯설어서 찾아보니 살균제, 불쾌한 부분을 제거하다라는 뜻을 가지고 있더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름처럼 Thread Sanitizer는 Thread를 사용함에 있어서 불쾌한 부분, 즉 &lt;b&gt;경쟁 상태가 발생하는 부분을 찾아줍니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 방법은 매우 간단합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;1192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dE4ZO2/btrtCVxcWcM/AfsA9jzP2RwgCyq8Cn4sz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dE4ZO2/btrtCVxcWcM/AfsA9jzP2RwgCyq8Cn4sz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dE4ZO2/btrtCVxcWcM/AfsA9jzP2RwgCyq8Cn4sz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdE4ZO2%2FbtrtCVxcWcM%2FAfsA9jzP2RwgCyq8Cn4sz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;570&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;1192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p25b7/btrtDhs4oGb/mevkafpkysC3udLPYvfeE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p25b7/btrtDhs4oGb/mevkafpkysC3udLPYvfeE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p25b7/btrtDhs4oGb/mevkafpkysC3udLPYvfeE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp25b7%2FbtrtDhs4oGb%2FmevkafpkysC3udLPYvfeE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;350&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Product -&amp;gt; Scheme -&amp;gt; Edit Scheme -&amp;gt; Run -&amp;gt; Diagnostics에서 Thread Sanitizer를 체크&lt;/i&gt;하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 사용하면 빌드 시간이 길어질 수 있으니 사용 후에는 다시 꺼주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 경쟁 상태가 발생하는 코드를 통해 확인해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1645169290879&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {

    private var sharedArray = [1, 2, 3, 4, 5] // 공유 자원
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.removeLast()
        }
        
        concurrentQueue.async {
            self.last()
        }
    }
    
    func last() {
        print(&quot;Start last()&quot;)
        print(self.sharedArray.last ?? 0)
        print(&quot;End last()&quot;)
    }
    
    func removeLast() {
        print(&quot;Start removeLast()&quot;)
        self.sharedArray.removeLast()
        print(&quot;End removeLast()&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 자원인 sharedArray의 마지막 원소를 출력하는 last() 메서드와 마지막 원소를 제거하는 removeLast() 메서드가 Concurrent한 Queue에서 비동기적으로 실행되고 있습니다.&lt;br /&gt;따라서 서로 다른 두 개의 스레드에서 동시에 공유자원에 접근할 수 있기 때문에 경쟁 상태가 발생할 수 있겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 Thread Sanitizer를 켜고 실행시키면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2492&quot; data-origin-height=&quot;1142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btaeR0/btruUnzF9oB/tibiQw0KTMpPxrGwkb2xR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btaeR0/btruUnzF9oB/tibiQw0KTMpPxrGwkb2xR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btaeR0/btruUnzF9oB/tibiQw0KTMpPxrGwkb2xR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtaeR0%2FbtruUnzF9oB%2FtibiQw0KTMpPxrGwkb2xR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2492&quot; height=&quot;1142&quot; data-origin-width=&quot;2492&quot; data-origin-height=&quot;1142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경쟁 상태가 나타나는 곳을 찾아 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thread9와 thread3에서 동시에 접근하고 있다고 알려줍니다.&lt;/p&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 Sanitizer를 사용하면 이러한 부분을 발견하고 해결할 수 있겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Thread Sanitizer를 사용한다고 항상 경쟁 상태를 발견하지는 못하는 것을 확인했습니다. &lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Thread Sanitizer를 맹신하기보다는 경쟁 상태를 이해하고 방지하는 것이 중요하다 생각합니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경쟁 상태가 발생하는 부분, 즉 Thread-Safe하지 않은 곳을 찾아내는 방법을 알아봤으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &amp;nbsp;Thread-Safe한 코드를 작성하는 방법을 알아봅시다.&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. NSLock&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/nslock&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/foundation/nslock&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646293962882&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/nslock&quot; data-og-url=&quot;https://developer.apple.com/documentation/foundation/nslock&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/nslock&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/nslock&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;NSLock은 Foundation이 제공하는 멀티 스레드의 작업을 조정하기 위한 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock() 메서드를 통해 Thread가 lock을 획득할 때까지 스레드를 Block 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unlock() 메서드를 통해 lock을 해제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 자원에 접근할 때 lock(), 완료되면 unlock()을 호출하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번에 하나의 스레드만 자원에 접근할 수 있으므로 Thread-Safe한 코드를 작성할 수 있습니다.&lt;/p&gt;
&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_1646294562385&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {
    
    private let lock: NSLock = NSLock()
    private var _sharedArray = [1, 2, 3, 4, 5] // 공유 자원
    
    private(set) var sharedArray: [Int] {
        get {
            self.lock.lock()
            defer { self.lock.unlock() }
            return _sharedArray
        }
        set {
            self.lock.lock()
            self._sharedArray = newValue
            self.lock.unlock()
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.removeLast()
        }
        
        concurrentQueue.async {
            self.last()
        }
    }
    
    func last() {
        print(&quot;Start last()&quot;)
        print(self.sharedArray.last ?? 0)
        print(&quot;End last()&quot;)
    }
    
    func removeLast() {
        print(&quot;Start removeLast()&quot;)
        self.sharedArray.removeLast()
        print(&quot;End removeLast()&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NSLock 객체를 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter와 setter에서 공유자원에 접근할 때 lock() 메서드를 호출하고 작업을 완료하면 unlock() 메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 스레드가 공유자원에 접근하고 있을 때 다른 스레드가 lock을 획득하지 못하기 때문에 한 번에 하나의 스레드만이 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread-Safe하다고 볼 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. DispatchSemaphore&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchSemaphore 또한 NSLock과 용도로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 가능한 Thread의 수를 1개로 제한하는 것이죠.&lt;/p&gt;
&lt;pre id=&quot;code_1646295364394&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {
    
    private let semaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
    private var _sharedArray = [1, 2, 3, 4, 5] // 공유 자원
    
    private(set) var sharedArray: [Int] {
        get {
            self.semaphore.wait()
            defer { self.semaphore.signal() }
            return _sharedArray
        }
        set {
            self.semaphore.wait()
            self._sharedArray = newValue
            self.semaphore.signal()
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.removeLast()
        }
        
        concurrentQueue.async {
            self.last()
        }
    }
    
    func last() {
        print(&quot;Start last()&quot;)
        print(self.sharedArray.last ?? 0)
        print(&quot;End last()&quot;)
    }
    
    func removeLast() {
        print(&quot;Start removeLast()&quot;)
        self.sharedArray.removeLast()
        print(&quot;End removeLast()&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NSLock과 DispatchSemaphore와 같이 Lock을 사용하여 공유자원에 접근을 제한하는 방법을 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이러한 방법은 교착 상태를 유발할 가능성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개 이상의 Lock을 사용하는 경우를 생각해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1646296170243&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {
    
    private let lockA: NSLock = NSLock()
    private let lockB: NSLock = NSLock()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.lockA.lock()
            self.funcA()
            self.lockB.lock()
            self.funcB()
            self.lockA.unlock()
            self.lockB.unlock()
        }
        
        concurrentQueue.async {
            self.lockB.lock()
            self.funcB()
            self.lockA.lock()
            self.funcA()
            self.lockB.unlock()
            self.lockA.unlock()
        }
    }
    
    func funcA() {
        print(#function)
    }
    
    func funcB() {
        print(#function)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;funcA()와 funcB()가 한 번에 하나의 스레드만이 접근해야 하는 임계 구역(Critical Section)이라고 할 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 메서드를 위해 lockA, lockB가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 함수를 모두 필요로 하는 작업 두 개가 존재할 때, 두 개의 작업이 서로가 서로의 lock이 해제되기를 무한정 기다리는 상황이 발생하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://h4njun.tistory.com/entry/동시성-프로그래밍5-Concurrency-Problems동시성과-관련된-문제들&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 포스트&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;따라서 Thread-Safe 한 코드를 작성하는 또 다른 방법을 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. SerialQueue + Sync&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;SerialQueue는 &lt;b&gt;한 번에 하나의 작업만&lt;/b&gt;을 순차적으로 실행시키는 직렬 큐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유자원에 접근하는 작업을 동일한 SerialQueue에서만 실행시킨다면??&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;또한 Async가 아닌 Sync로 작업을 실행하게 되면 모든 스레드에서 일관된 값을 얻을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646303743769&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {
    
    private let serialQueue: DispatchQueue = DispatchQueue.init(label: &quot;com.hanjun.serial&quot;)
    private var sharedArray = [1, 2, 3, 4, 5] // 공유 자원
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.serialQueue.sync {
                self.removeLast()
            }
        }
        
        concurrentQueue.async {
            self.serialQueue.sync {
                self.last()
            }
        }
        
        // Here
        concurrentQueue.async {
            self.serialQueue.async {
                self.removeLast()
            }
            print(self.sharedArray) //[1,2,3,4]
        }
    }
    
    func last() {
        print(&quot;Start last()&quot;)
        print(self.sharedArray.last ?? 0)
        print(&quot;End last()&quot;)
    }
    
    func removeLast() {
        print(&quot;Start removeLast()&quot;)
        self.sharedArray.removeLast()
        print(&quot;End removeLast()&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 작업을 sync 하게 실행시키지 않는다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;concurrentQueue에서 실행시킨 sharedArray를 출력하는 작업이 즉시 반환되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;removeLast()가 실행되기 이전 결과인 [1,2,3,4]가 출력됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646304497097&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {
    
    private let serialQueue: DispatchQueue = DispatchQueue.init(label: &quot;com.hanjun.serial&quot;)
    private var sharedArray = [1, 2, 3, 4, 5] // 공유 자원
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.serialQueue.sync {
                self.removeLast()
            }
        }
        
        concurrentQueue.async {
            self.serialQueue.sync {
                self.last()
            }
        }
        
        // Here
        concurrentQueue.async {
            self.serialQueue.sync {
                self.removeLast()
            }
            print(self.sharedArray) //[1,2,3]
        }
    }
    
    func last() {
        print(&quot;Start last()&quot;)
        print(self.sharedArray.last ?? 0)
        print(&quot;End last()&quot;)
    }
    
    func removeLast() {
        print(&quot;Start removeLast()&quot;)
        self.sharedArray.removeLast()
        print(&quot;End removeLast()&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 sync로 작업을 진행하게 되면 작업을 보내는 스레드가 보낸 작업이 완료될 때까지 기다리기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;removeLast()가 실행된 후의 결과인 [1,2,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;물론 현재 스레드가 메인 스레드일 경우에는 sync로 작업을 보내면 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 &lt;a href=&quot;https://h4njun.tistory.com/entry/동시성-프로그래밍3-DispatchQueue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이곳에서&lt;/a&gt; 알아보았습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. DispatchBarrier&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;SerialQueue + Sync를 사용하게 되면 매우 엄격한 Thread-Safe 코드를 작성하는 방법을 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 작업과 쓰기 작업 모두 한 번에 하나씩만 실행하기에 매우 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 조금 비효율적이라는 생각이 들기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 작업의 경우에는 동시에 실행이 되어도 공유 자원의 영향을 끼치지 않기 때문에 Thread-Safe 하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Concurrent Queue를 사용하면서 &lt;b&gt;특정 작업에 경우에만 Serial하게 동작&lt;/b&gt;시키고 싶은 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 경우 사용할 수 있는 것이 &lt;b&gt;Dispatch Barrier&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatch_barrier/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/dispatch/dispatch_barrier/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646807086949&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/dispatch/dispatch_barrier/&quot; data-og-url=&quot;https://developer.apple.com/documentation/dispatch/dispatch_barrier/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatch_barrier/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/dispatch/dispatch_barrier/&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;
&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;953&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8NiTh/btrvtcEpSlL/hPok1MDBWhLYi1VZssP2Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8NiTh/btrvtcEpSlL/hPok1MDBWhLYi1VZssP2Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8NiTh/btrvtcEpSlL/hPok1MDBWhLYi1VZssP2Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8NiTh%2FbtrvtcEpSlL%2FhPok1MDBWhLYi1VZssP2Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;953&quot; height=&quot;119&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchQueue의 async 메서드 중 위와 같이 DispatchWorkItemFlag를 요구하는 메서드가 존재합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cihNPL/btrvxkHJPm1/ig8Ki8ICkU4hZuBe8n2OI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cihNPL/btrvxkHJPm1/ig8Ki8ICkU4hZuBe8n2OI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cihNPL/btrvxkHJPm1/ig8Ki8ICkU4hZuBe8n2OI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcihNPL%2FbtrvxkHJPm1%2Fig8Ki8ICkU4hZuBe8n2OI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1252&quot; height=&quot;631&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 barrier 변수를 넣어주면 Dispatch Barrier를 사용할 수 있습니다.&lt;/p&gt;
&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;다른 스레드는 해당 작업이 완료될 때까지 Block 됩니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMtk04/btrvvCWAQgw/jccmBybKgnyUVmada6CRi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMtk04/btrvvCWAQgw/jccmBybKgnyUVmada6CRi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMtk04/btrvvCWAQgw/jccmBybKgnyUVmada6CRi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMtk04%2FbtrvvCWAQgw%2FjccmBybKgnyUVmada6CRi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1866&quot; height=&quot;966&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통해 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646823607114&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
    
final class ViewController: UIViewController {
    
    private var sharedArray = [1, 2, 3, 4, 5] // 공유 자원
    private let secondConcurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.secondConcurrent&quot;, attributes: .concurrent)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let concurrentQueue = DispatchQueue.init(label: &quot;com.hanjun.concurrent&quot;, attributes: .concurrent)
        
        concurrentQueue.async {
            self.removeLast()
        }
        
        concurrentQueue.async {
            self.last()
        }
    }   
    
    func last() {
        self.secondConcurrentQueue.sync {
            print(&quot;Start last()&quot;)
            print(self.sharedArray.last ?? 0)
            print(&quot;End last()&quot;)
        }
    }
    
    func removeLast() {
        self.secondConcurrentQueue.async(flags: .barrier) {
            print(&quot;Start removeLast()&quot;)
            self.sharedArray.removeLast()
            print(&quot;End removeLast()&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;last() 메서드와 removeLast() 메서드를 확인하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;last() 메서드의 경우 읽기 작업이기 때문에 sync로 작업을 실행합니다.(async로 작업을 실행하지 않는 이유는 값을 즉시 반환하면 안 되고 정확한 값을 읽어야 하기 때문입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;removeLast() 메서드의 경우는 쓰기 작업이기 때문에 경쟁 상황이 발생해서는 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 barrier를 사용해 removeLast() 메서드만이 실행되도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Dispatch Barrier를 사용하면 Concurrent Queue를 사용해도 Thread-Safe 한 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Thread-Safe한 코드를 작성하는 방법을 알아보았습니다.&lt;/p&gt;
&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;만약 다른 스레드에서 접근해야 한다면 위의 방법들을 이용하여 Thread-Safe 한 코드를 작성할 수 있도록 해야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ref.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645166481866&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;iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션&quot; data-og-description=&quot;동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., - 강의 소개 | 인프런...&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cnvVSt/hyNrEPigT7/WtYLoENHlas7aYvr7Adm81/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/3MHCF/hyNrC41GvZ/ruDqiVVgJSizfGIDJNvxt0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cnvVSt/hyNrEPigT7/WtYLoENHlas7aYvr7Adm81/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/3MHCF/hyNrC41GvZ/ruDqiVVgJSizfGIDJNvxt0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., - 강의 소개 | 인프런...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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://varun04tomar.medium.com/thread-safety-in-ios-to-avoid-race-conditions-read-and-write-problem-deadlock-swift-4a583e1b8a95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://varun04tomar.medium.com/thread-safety-in-ios-to-avoid-race-conditions-read-and-write-problem-deadlock-swift-4a583e1b8a95&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646288205436&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;Thread safety in iOS to avoid race conditions/read and write problem /deadlock&amp;nbsp;: Swift&quot; data-og-description=&quot;Multithreading is one of the most important concept of any software or hardware&quot; data-og-host=&quot;varun04tomar.medium.com&quot; data-og-source-url=&quot;https://varun04tomar.medium.com/thread-safety-in-ios-to-avoid-race-conditions-read-and-write-problem-deadlock-swift-4a583e1b8a95&quot; data-og-url=&quot;https://varun04tomar.medium.com/thread-safety-in-ios-to-avoid-race-conditions-read-and-write-problem-deadlock-swift-4a583e1b8a95&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pBBjs/hyNAORLr7B/p2chMwJkFQcKy8C4DXDvW1/img.jpg?width=1200&amp;amp;height=739&amp;amp;face=0_0_1200_739,https://scrap.kakaocdn.net/dn/bybsB9/hyNAMfmspN/wc8eEFxZnmFB8aklCZjzS1/img.jpg?width=1400&amp;amp;height=863&amp;amp;face=0_0_1400_863,https://scrap.kakaocdn.net/dn/MGIfF/hyNAUxFWx9/UOquxCUpYW44KzMpUjO7TK/img.png?width=1400&amp;amp;height=863&amp;amp;face=0_0_1400_863&quot;&gt;&lt;a href=&quot;https://varun04tomar.medium.com/thread-safety-in-ios-to-avoid-race-conditions-read-and-write-problem-deadlock-swift-4a583e1b8a95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://varun04tomar.medium.com/thread-safety-in-ios-to-avoid-race-conditions-read-and-write-problem-deadlock-swift-4a583e1b8a95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pBBjs/hyNAORLr7B/p2chMwJkFQcKy8C4DXDvW1/img.jpg?width=1200&amp;amp;height=739&amp;amp;face=0_0_1200_739,https://scrap.kakaocdn.net/dn/bybsB9/hyNAMfmspN/wc8eEFxZnmFB8aklCZjzS1/img.jpg?width=1400&amp;amp;height=863&amp;amp;face=0_0_1400_863,https://scrap.kakaocdn.net/dn/MGIfF/hyNAUxFWx9/UOquxCUpYW44KzMpUjO7TK/img.png?width=1400&amp;amp;height=863&amp;amp;face=0_0_1400_863');&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;Thread safety in iOS to avoid race conditions/read and write problem /deadlock&amp;nbsp;: Swift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Multithreading is one of the most important concept of any software or hardware&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;varun04tomar.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>iOS</category>
      <author>H4NJUN</author>
      <guid isPermaLink="true">https://h4njun.tistory.com/23</guid>
      <comments>https://h4njun.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D6-Race-Condition%EA%B2%BD%EC%9F%81-%EC%83%81%ED%83%9C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95#entry23comment</comments>
      <pubDate>Wed, 9 Mar 2022 20:14:28 +0900</pubDate>
    </item>
  </channel>
</rss>