<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>PinguiOS</title>
    <link>https://icksw.tistory.com/</link>
    <description>iOS 개발자가 되기 위해 iOS 공부, Swift 공부, CS 공부를 하고 있습니다.</description>
    <language>ko</language>
    <pubDate>Mon, 1 Jun 2026 00:17:23 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Dev_Pingu</managingEditor>
    <image>
      <title>PinguiOS</title>
      <url>https://tistory1.daumcdn.net/tistory/4063297/attach/ca25aab9202c4ce092d1b19690f9616c</url>
      <link>https://icksw.tistory.com</link>
    </image>
    <item>
      <title>[iOS] Coordinator pattern 공부 - 1</title>
      <link>https://icksw.tistory.com/296</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&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에서의 Coordinator Pattern이라는 패턴에 대해 알아보는 글을 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부하다 보니 이 패턴을 만든 사람이 있었습니다. KHANLOU라는 블로그를 운영하는 사람인데.. 거기에 &lt;a href=&quot;https://khanlou.com/2015/01/the-coordinator/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The Coordinator&lt;/a&gt;라는 글이 코디네이터 패턴의 시작..? 같습니다. 그리고 이 글을 쓰고 나서 몇 달 뒤에 좀 더 정리해서 &lt;a href=&quot;https://khanlou.com/2015/10/coordinators-redux/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Coordinators Redux&lt;/a&gt;라는 글을 또 작성했는데, 이 두 개의 글을 읽고 정리해 보겠습니다.&lt;/p&gt;
&lt;h1&gt;Coordinator Pattern 탄생 배경&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글이 2015년에 작성되었는데 그때부터 iOS 개발자들은 이미 Massive ViewController가 문제가 되고 있었던 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사람이 코디네이터 패턴을 생각하게 된 기존 문제점은 3개였다고 해요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;AppDelegate에 너무 많은 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(지금은 SceneDelegate이지만..) AppDelegate가 앱 진입점이다 보니 이것저것 넣기 쉬운 지점인데, rootViewController를 구성하기 위한 로직도 여기에 존재하는 경우가 많았다고 합니다.&lt;/li&gt;
&lt;li&gt;근데 RootViewController를 구성하는 책임이 AppDelegate에 있느냐?에 대한 의문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ViewController에 너무 많은 책임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 사람이 생각하기에 문제 되는 VC는 아래와 같은 책임들을 가지고 있다고 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Model - View Binding&lt;/li&gt;
&lt;li&gt;Subview Allocation&lt;/li&gt;
&lt;li&gt;Data Fetching&lt;/li&gt;
&lt;li&gt;Layout&lt;/li&gt;
&lt;li&gt;Data Transformation&lt;/li&gt;
&lt;li&gt;Navigation Flow&lt;/li&gt;
&lt;li&gt;User Input&lt;/li&gt;
&lt;li&gt;Model Mutation&lt;/li&gt;
&lt;li&gt;기타 등등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 모든 게 VC에 존재하니까 Massive VC가 되는 것;&lt;/li&gt;
&lt;li&gt;그럼 VC는 뭘 해야 할까?를 고민하다 보면 우리는 Controller라는 단어에 가스라이팅 당한 걸 지도 모릅니다. 이름 때문에 뭐든지 다 VC가 해도 될 것만 같은 거죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Smooth Flow
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;navigation flow를 VC에서 처리하게 되면 이는 VC가 자신의 부모인 navigationController에게 명령을 내리는 행위인데, 느낌상 자식이 부모에게 명령하는.. 버릇없는 행동이라고 생각한다고 합니다.ㅋㅋ&lt;/li&gt;
&lt;li&gt;또한 이 사람은 코딩세계에서는 Child가 Parent를 모르는 게 좋다고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 flow logic이 뭐냐!&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let selectData = self.data[indexPath.item]
    let detailVC = DetailViewController(selectData)
    self.navigationController.pushViewController(detailVC, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 어떤 VC를 생성해서 NavigationController에 push 하는 로직을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 VC 자체에 flow logic이 존재하게 되면 해당 로직들이 분산될 뿐 아니라, 각각의 VC가 자신의 다음 flow에 해당하는 VC들의 정보를 모두 알고 있어야 한다는 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이 사람은 Library와 Framework 관점에서 개발자는 프레임워크인 UIKit를 마치 라이브러리처럼 다뤄야 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 두 개의 차이점은 사용자는 라이브러리를 호출해서 사용하며, 프레임워크는 사용자를 호출해서 사용한다! 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 요걸 이렇게 이해했어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UIKit을 Framework로 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프레임워크가 viewDidLoad를 호출해 줄 때까지 개발자는 원하는 코드를 실행할 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UIKit을 Library로 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;viewDidLoad를 기다리는 것이 아닌, 원하는 시점에 원하는 코드를 실행할 수 있음.&lt;/li&gt;
&lt;li&gt;즉 UIKit에서 벗어나 코드의 흐름을 완전히 제어할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;그래서 Coordinator가 뭔데요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 Coordinator는 하나 이상의 VC를 지배하는 객체입니다.&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;그래서 아까 그 많던 VC의 책임 중 어떤 것들을 Coordinator로 넘겨주느냐,, 하면 이 사람 생각은 flow logic, model mutation 책임을 넘겨주자고 합니다. (model mutation은 DB write 혹은 REST API PUT, POST 등을 의미한다고 해요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해야 VC는 데이터를 사용하기만 하고, 변경은 할 수 없게 된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 VC는 그저 데이터를 뷰로 보여주기만 할 뿐 아무것도 못하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사용자 입력이나 이벤트들을 delegate와 같은 방법으로 처리해줘야 하죠.&lt;/p&gt;
&lt;h1&gt;Coordinator가 뭐가 좋은데요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이걸 쓰면 뭐가 좋냐!&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;VC가 독립적으로 존재하게 된다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VC는 데이터를 보여줄 뿐 아무것도 못 함&lt;/li&gt;
&lt;li&gt;이벤트 발생 시 delegate와 같은 방법으로 처리해야 하는데 VC는 이를 처리하는 주체가 누군지도 모름&lt;/li&gt;
&lt;li&gt;A/B 테스트 혹은 여러 개의 플로우가 필요한 경우엔 VC에 조건문을 붙이는 게 아니라 코디네이터 객체를 바꾸면 된다&lt;/li&gt;
&lt;li&gt;모든 flow logic이 각각의 VC에서 한 곳에 모이게 되므로 이해가 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;VC가 재사용 가능해진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버튼이 어떤 용도인지와 같은 것을 가정하지 않는다 (버튼이 눌렸다는 이벤트만 넘겨줄 뿐)&lt;/li&gt;
&lt;li&gt;flow logic을 복붙 할 필요 없이 그대로 재사용이 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앱의 모든 하위 작업을 일관된 방법으로 캡슐화가 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 VC의 이벤트에 의해 동일한 작업이 수행될 때 하나의 코드로 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;뷰 바인딩과 side effect를 분리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VC가 데이터를 변경할 수 없으니, VC에서 데이터가 잘못 변경될 것을 걱정할 필요가 없어짐 (어떻게 보면 단방향 개발?)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Coordinator는 온전히 개발자가 지배할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아까 위에서 언급한 대로 UIKit을 라이브러리처럼 활용할 수 있으므로 원하는 시점에 원하는 코드 호출이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 많은 장점을 가진 Coordinator를 사용하면 앱과 코드를 관리하기 쉽게 만드는데 도움을 주고, VC가 쉽게 재사용가능하므로 앱을 성장하는데 많은 도움을 준다고 하며 글은 끝납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 조금 아쉬운 게, 창시자가 코디네이터 패턴에 대한 라이브러리나 프로토콜 같은 걸 진심으로 정의해두진 않았습니다;&lt;br /&gt;물론 예제 코드가 있긴 해서 코디네이터 패턴을 공부하신 분들의 코드를 보면 대체로 해당 예제 코드를 응용한 거 같았어요.&lt;/p&gt;
&lt;p 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;</description>
      <category>Development/Architecture</category>
      <category>Coordinator</category>
      <category>design</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Swift</category>
      <category>아키텍처</category>
      <category>코디네이터</category>
      <category>패턴</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/296</guid>
      <comments>https://icksw.tistory.com/296#entry296comment</comments>
      <pubDate>Wed, 4 Sep 2024 00:43:02 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2021] Meet AsyncSequence</title>
      <link>https://icksw.tistory.com/293</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&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/await를 다시금 공부 중이라 오늘은 WWDC 2021에 발표된 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10058/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Meet AsyncSequence&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;AsyncSequence를 간단하게 요약하면 기존에 있던 Sequence에 비동기 기능을 추가한 녀석인 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Meet AsyncSequence&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상은 일단 간단하게 AsyncSequence의 새로운 기능을 설명하기 위한 예제 코드로 시작됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7tacv/btr1f15kv2P/lB1Mqn81AVWFlRAu9ty8B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7tacv/btr1f15kv2P/lB1Mqn81AVWFlRAu9ty8B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7tacv/btr1f15kv2P/lB1Mqn81AVWFlRAu9ty8B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7tacv%2Fbtr1f15kv2P%2FlB1Mqn81AVWFlRAu9ty8B1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;267&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 endPointURL을 통해 csv파일을 다운로드하는 작업인 것을 알 수 있습니다.&lt;/p&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/await 와 AsyncSequence를 사용하면 된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;csv파일이니 각각의 라인이 하나의 데이터이니 위 코드의&amp;nbsp;반복문에서 라인단위로 처리를 해주는 것을 볼 수 있어요. 그리고 이때 사용한 for문은 기존 for-in이 아닌 for-try-await-in 구문인 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 기존에 위 작업을 처리하려면 csv파일이 모두 다운된 뒤 작업을 처리해야 했을 뿐 아니라 completion handler와 같은 콜백함수로 작업을 처리했을 건데 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;게다가 dropFirst()와 같이 기존 Sequence 타입에 사용하던 api를 그대로 사용하는 것도 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 코드에서 async function에 대해 &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;Async Function
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;await 키워드를 사용해서 콜백 없이 concurrent 코드를 작성할 수 있다.&lt;/li&gt;
&lt;li&gt;Async Function을 호출하면 값이나 에러가 발생할 때 suspend 되고 다시 resume 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 AsyncSequence와의 차이점은 AsyncSequence는 각각의 Element에서 suspend 되고 iterator가 값을 생성하거나 에러를 던질 때 다시 resume 된다는 것입니다.&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;AsyncSequence&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 AsyncSequence를 좀 더 자세히 알아볼게요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdFt3o/btr01Ul2ptR/ivbbc9QkRaTKyTcKAhn1R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdFt3o/btr01Ul2ptR/ivbbc9QkRaTKyTcKAhn1R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdFt3o/btr01Ul2ptR/ivbbc9QkRaTKyTcKAhn1R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdFt3o%2Fbtr01Ul2ptR%2Fivbbc9QkRaTKyTcKAhn1R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;262&quot; height=&quot;174&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AsyncSequence&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름에서 알 수 있듯 기존 Sequence와 동일한 부분이 있지만 차이점이 있다.&lt;/li&gt;
&lt;li&gt;차이점은 각 Element가 비동기로 전달된다.&lt;/li&gt;
&lt;li&gt;AsyncSequence는 에러를 던질 수 있다.&lt;/li&gt;
&lt;li&gt;0개 이상의 값이 될 수 있고 Sequence와 마찬가지로 iterator에서 nil을 반환하면 완료된다.&lt;/li&gt;
&lt;li&gt;에러가 발생하면 iterator의 이후 next 호출에서 nil이 반환되어 완료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 기존 iteration은 어떻게 동작했는지 먼저 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba5PQe/btr1lAzGBH4/aW8i1YL5E7rNXYLmGofM9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba5PQe/btr1lAzGBH4/aW8i1YL5E7rNXYLmGofM9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba5PQe/btr1lAzGBH4/aW8i1YL5E7rNXYLmGofM9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba5PQe%2Fbtr1lAzGBH4%2FaW8i1YL5E7rNXYLmGofM9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;111&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 정말 많이 본 흔한 for-in 코드입니다.&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;290&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca1YvX/btr1bejWhyN/jx4Ac09edRQXUjvsUkGn70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca1YvX/btr1bejWhyN/jx4Ac09edRQXUjvsUkGn70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca1YvX/btr1bejWhyN/jx4Ac09edRQXUjvsUkGn70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca1YvX%2Fbtr1bejWhyN%2Fjx4Ac09edRQXUjvsUkGn70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;164&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;131&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;iterator를 만들어서 nil이 나올 때까지 while 루프를 돌리는 거죠.&lt;/p&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/await 기능을 사용하고 싶다! 라고 하신다면 아래와 같이 간단하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPXOde/btr1pWvtea6/4XXKded6CEQrzkzcl4WzK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPXOde/btr1pWvtea6/4XXKded6CEQrzkzcl4WzK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPXOde/btr1pWvtea6/4XXKded6CEQrzkzcl4WzK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPXOde%2Fbtr1pWvtea6%2F4XXKded6CEQrzkzcl4WzK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;411&quot; height=&quot;153&quot; data-origin-width=&quot;293&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;그리고 이걸 다시 for 루프로 바꾸면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;281&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJHB1/btr1lz8FnJo/sAiWnJmryaiUZnHgzCWNw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJHB1/btr1lz8FnJo/sAiWnJmryaiUZnHgzCWNw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJHB1/btr1lz8FnJo/sAiWnJmryaiUZnHgzCWNw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJHB1%2Fbtr1lz8FnJo%2FsAiWnJmryaiUZnHgzCWNw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;377&quot; height=&quot;212&quot; data-origin-width=&quot;281&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되겠네요. 만약 에러를 던지는 AsyncSequence라면 for-try-await-in 구문을 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxDMsl/btr1bf4iUZR/MKUuxH5vKQ2ZKV6hc6xkOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxDMsl/btr1bf4iUZR/MKUuxH5vKQ2ZKV6hc6xkOK/img.png&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;173&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.43&quot; style=&quot;width: 49.840574%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxDMsl/btr1bf4iUZR/MKUuxH5vKQ2ZKV6hc6xkOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxDMsl%2Fbtr1bf4iUZR%2FMKUuxH5vKQ2ZKV6hc6xkOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AMrSI/btr1dcsBnab/kfKynbq1eMqM7LKIEUNJHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AMrSI/btr1dcsBnab/kfKynbq1eMqM7LKIEUNJHk/img.png&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;177&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.57&quot; style=&quot;width: 48.996635%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AMrSI/btr1dcsBnab/kfKynbq1eMqM7LKIEUNJHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAMrSI%2Fbtr1dcsBnab%2FkfKynbq1eMqM7LKIEUNJHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;347&quot; height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 기존 for-in 구문과 마찬가지로 break, continue도 사용할 수 있습니다.&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;342&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIDGPe/btr1bkYNAxd/Jt8pROpOgX0J5Y6osRHHSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIDGPe/btr1bkYNAxd/Jt8pROpOgX0J5Y6osRHHSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIDGPe/btr1bkYNAxd/Jt8pROpOgX0J5Y6osRHHSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIDGPe%2Fbtr1bkYNAxd%2FJt8pROpOgX0J5Y6osRHHSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;312&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 위와 같이 작성하면 에러를 처리할 수도 있게 됩니다. 함수가 throw 함수일 때와 마찬가지로 throw AsyncSequence라면 try를 쓰지 않았을 때 컴파일러가 친절하게 알려줍니다.&lt;/p&gt;
&lt;p 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZRfQh/btr1ddkHys9/B45EPpB44wzEDttpQtKt91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZRfQh/btr1ddkHys9/B45EPpB44wzEDttpQtKt91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZRfQh/btr1ddkHys9/B45EPpB44wzEDttpQtKt91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZRfQh%2Fbtr1ddkHys9%2FB45EPpB44wzEDttpQtKt91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;291&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 위 코드에서와 같이 cancel()을 호출하면 명시적으로 취소할 수도 있습니다.&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;근데 위 코드는 2021년에 나와서 async인 거 같은데 현재는 아래와 같이 Task로 사용해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677668191924&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let iteration1 = Task {
	for await quake in quakes {
    	//...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 AsyncSequence가 무기한으로 실행될 수 있는 걸 알고 있을 때 유용하게 사용할 수 있다고 합니다.&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;AsyncSqeucnce API in iOS 15&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서 AsyncSequence가 적용된 API를 소개하네요.&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;585&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exgMDD/btr2zmso0fM/dD5GkLjJikxvJ8Es81uJqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exgMDD/btr2zmso0fM/dD5GkLjJikxvJ8Es81uJqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exgMDD/btr2zmso0fM/dD5GkLjJikxvJ8Es81uJqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexgMDD%2Fbtr2zmso0fM%2FdD5GkLjJikxvJ8Es81uJqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;278&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 FileHandle에는 해당 FileHandle의 비동기 바이트 시퀀스에 대한 접근을 제공하는 새로운 프로퍼티인 bytes가 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/neBXN/btr2u0wMPrR/waFpKo6nsw5tOuK1U9i8OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/neBXN/btr2u0wMPrR/waFpKo6nsw5tOuK1U9i8OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/neBXN/btr2u0wMPrR/waFpKo6nsw5tOuK1U9i8OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FneBXN%2Fbtr2u0wMPrR%2FwaFpKo6nsw5tOuK1U9i8OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;264&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 bytes 프로퍼티를 위와 같이 비동기 바이트 시퀀스를 라인단위로 변환하는 새로운 api인 lines와 함께 사용하면 라인단위로 파일을 처리할 수 있다고 하네요.&lt;/p&gt;
&lt;p 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fh04M/btr2voYAJIM/6dFkazfFZTqnl0jeruAUFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fh04M/btr2voYAJIM/6dFkazfFZTqnl0jeruAUFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fh04M/btr2voYAJIM/6dFkazfFZTqnl0jeruAUFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFh04M%2Fbtr2voYAJIM%2F6dFkazfFZTqnl0jeruAUFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;322&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 URLSession에는 URL, URLRequest가 주어졌을 때 비동기 바이트 시퀀스를 가져오는 bytes 메서드가 있다고 합니다. 위와 같이 이를 활용하여 응답 및 인증을 잘 처리할 수 있다고 간단히 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession의 새로운 기능을 자세히 알고 싶으면 &quot;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10095/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Use async/await with URLSession&lt;/a&gt;&quot; 이라는 영상을 보라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notification에도 AsyncSequence를 적용할 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzchH/btr2u0KePtf/GH37qaEfgP9i6ONqtvuHV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzchH/btr2u0KePtf/GH37qaEfgP9i6ONqtvuHV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzchH/btr2u0KePtf/GH37qaEfgP9i6ONqtvuHV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzchH%2Fbtr2u0KePtf%2FGH37qaEfgP9i6ONqtvuHV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;292&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 새로운 API가 생겼고 이를 활용하면 된다고 하네요. 위 코드를 보면 기존 Sequence 타입에 사용하던 first(where:)와 같은 기능들도 사용할 수 있어 적용하기도 쉽고 적용 시 코드가 간결하고 읽기 쉬워질 거라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnnCpZ/btr2zlNNDpl/m6LwUMKJQsMGbcKKAK5dKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnnCpZ/btr2zlNNDpl/m6LwUMKJQsMGbcKKAK5dKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnnCpZ/btr2zlNNDpl/m6LwUMKJQsMGbcKKAK5dKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnnCpZ%2Fbtr2zlNNDpl%2Fm6LwUMKJQsMGbcKKAK5dKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;183&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 많은 api를 만들어뒀으니 잘 사용해 달라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Build your own&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 새로운 AsyncSequence가 뭔지도 알겠고 뭐가 새로 나왔는지도 알겠는데, 그래서 기존 코드에 어떻게 쓰라고?라는 생각이 들 때쯤 그 방법을 알려줍니다. &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dijLuM/btr2m2Jj7fI/0sdzKEQgM5LXmP6aKFQjDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dijLuM/btr2m2Jj7fI/0sdzKEQgM5LXmP6aKFQjDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dijLuM/btr2m2Jj7fI/0sdzKEQgM5LXmP6aKFQjDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdijLuM%2Fbtr2m2Jj7fI%2F0sdzKEQgM5LXmP6aKFQjDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;168&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드는 아마도 위와 같이 콜백이나 델리게이트 패턴을 활용해서 비동기 작업을 처리하고 있었을 거예요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n0WdE/btr2sYzt9Aq/7KGNAucx1onw0zWMHHNw0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n0WdE/btr2sYzt9Aq/7KGNAucx1onw0zWMHHNw0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n0WdE/btr2sYzt9Aq/7KGNAucx1onw0zWMHHNw0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn0WdE%2Fbtr2sYzt9Aq%2F7KGNAucx1onw0zWMHHNw0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;396&quot; height=&quot;185&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드에 AsyncSequence를 어떻게 적용하는지 보여주기 위해 위와 같은 코드를 예시로 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 핸들러 패턴을 사용한 코드인걸 알 수 있어요. 동일한 인터페이스를 새로운 AsyncStream 타입을 사용해 작성해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8gAbQ/btr2gkwGkBO/KRlAveTw1J7SqPiU4hyKr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8gAbQ/btr2gkwGkBO/KRlAveTw1J7SqPiU4hyKr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8gAbQ/btr2gkwGkBO/KRlAveTw1J7SqPiU4hyKr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8gAbQ%2Fbtr2gkwGkBO%2FKRlAveTw1J7SqPiU4hyKr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;240&quot; data-origin-width=&quot;465&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;AsyncStream을 사용해서 기존 코드를 위와 같이 쉽게 바꿀 수 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;continuation이라는 애가 나오는데 얘는 값을 두 번 이상 yeield 하거나 finish 혹은 termination을 처리하는 애입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 위와 같이 코드를 작성하면 하나의 콘텍스트에서 monitor를 생성 및 quake를 yield 하고 작업의 termination을 처리하고, 작업을 시작할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b48qDn/btr2nFHbRDH/jSKVdH5hKD2kO7ehw8QEf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b48qDn/btr2nFHbRDH/jSKVdH5hKD2kO7ehw8QEf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b48qDn/btr2nFHbRDH/jSKVdH5hKD2kO7ehw8QEf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb48qDn%2Fbtr2nFHbRDH%2FjSKVdH5hKD2kO7ehw8QEf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;186&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위와 같이 직접 만든 AsyncStream을 사용하면 됩니다.&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;570&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo8QcG/btr133oMPmh/HnLE9yPFakKwzoySqYOBUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo8QcG/btr133oMPmh/HnLE9yPFakKwzoySqYOBUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo8QcG/btr133oMPmh/HnLE9yPFakKwzoySqYOBUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo8QcG%2Fbtr133oMPmh%2FHnLE9yPFakKwzoySqYOBUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;323&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 AsyncStream은 여러분이 직접 Async Sequence를 만들기 쉽게 해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 safety, iteration, cancellation와 같이 Async Sequence에서 기대하는 모든 것을 처리할 수 있고 buffering도 처리할 수 있기 때문에&amp;nbsp;AsyncStream은 기존 코드를 Async Sequence로 만들 때 사용하기 좋습니다.&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;565&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rvsOK/btr2aJKm2x6/KeLA7KoUVHO8fN2ZZrpYqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rvsOK/btr2aJKm2x6/KeLA7KoUVHO8fN2ZZrpYqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rvsOK/btr2aJKm2x6/KeLA7KoUVHO8fN2ZZrpYqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrvsOK%2Fbtr2aJKm2x6%2FKeLA7KoUVHO8fN2ZZrpYqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;312&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 에러를 처리해야 할 땐 AsyncThrowingStream을 사용하면 됩니다. AsyncThrowingStream은 다른 건 다 AsyncStream과 동일하지만 iteration에서 에러를 던져서 실패를 처리할 수 있다는 차이점만 있습니다.&lt;/p&gt;
&lt;p 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;영상을 다 정리하고 나니 처음 요약한 대로 Sequence 타입에 비동기 기능만 추가한 것이 AsyncSequence였던 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 비동기 코드를 이걸 활용해 고쳐나가면 코드를 훨씬 깔끔하고 직관적으로 작성할 수 있을 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상을 정리하면서 비동기 처리 하는 예시들을 많이 볼 수 있어 바로 적용해 볼 수 있을 거 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨길 바라며, 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Apple/WWDC 2021</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/293</guid>
      <comments>https://icksw.tistory.com/293#entry293comment</comments>
      <pubDate>Tue, 7 Mar 2023 01:42:23 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2020] Modern Cell Configuration</title>
      <link>https://icksw.tistory.com/292</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 WWDC 2020이 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10027/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Modern Cell Configuration&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;이번 영상을 보고 느낀 점을 요약하자면, &quot;컬렉션 뷰, 테이블 뷰의 셀을 구성하는 새로운 방법에 대해 설명하는 영상!&quot;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Modern Cell Configuration&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmmAJP/btrYVhhztz7/o9R0pMXoXlK5MrkpvsqFv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmmAJP/btrYVhhztz7/o9R0pMXoXlK5MrkpvsqFv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmmAJP/btrYVhhztz7/o9R0pMXoXlK5MrkpvsqFv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmmAJP%2FbtrYVhhztz7%2Fo9R0pMXoXlK5MrkpvsqFv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1124&quot; height=&quot;432&quot; data-origin-width=&quot;1124&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;iOS 14부터 기존 기술을 기반으로 UICollectionView에 위 그림과 같이 세 가지로 나눌 수 있는 기능을 제공한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지에 대한 소개 영상은 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10097/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Advances in Collection View&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;이번 영상은 제목에서도 알 수 있듯이 View Configuration에 대해 주로 다룰 예정이라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eavWWs/btrYTWkGOCP/JGBCrRo3kVCPKvqZuURCo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eavWWs/btrYTWkGOCP/JGBCrRo3kVCPKvqZuURCo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eavWWs/btrYTWkGOCP/JGBCrRo3kVCPKvqZuURCo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeavWWs%2FbtrYTWkGOCP%2FJGBCrRo3kVCPKvqZuURCo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;287&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 세 가지를 알려준다고 합니다. 풀어쓰자면, 새로운 개념인 configuration으로 셀을 설정하는 방법을 알아보고, configuration state, background configuration, content configuration까지 알아볼 예정인 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 먼저 Getting started with configurations에 대해 알아볼게요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Getting started with configurations&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 configuration을 사용하기 전에는 셀을 어떻게 만들었는지 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sHkV3/btrYTNVDC8R/xUqyUsz3XdKLr0HiVszdO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sHkV3/btrYTNVDC8R/xUqyUsz3XdKLr0HiVszdO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sHkV3/btrYTNVDC8R/xUqyUsz3XdKLr0HiVszdO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsHkV3%2FbtrYTNVDC8R%2FxUqyUsz3XdKLr0HiVszdO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1186&quot; height=&quot;398&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 셀을 위와 같은 방식으로 만들곤 했습니다. 그런데 이제 새로운 configuration api를 사용하면!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0Z8hD/btrYRVNTDGU/31LV1k8A5W34OeMSQ0NbNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0Z8hD/btrYRVNTDGU/31LV1k8A5W34OeMSQ0NbNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0Z8hD/btrYRVNTDGU/31LV1k8A5W34OeMSQ0NbNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0Z8hD%2FbtrYRVNTDGU%2F31LV1k8A5W34OeMSQ0NbNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;456&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;456&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;일단 간단하게 위 코드를 설명해 보자면, defaultContentConfiguration()이라는 기본 스타일을 반환하고, 이러저러한 것들을 원하는 대로 설정한 뒤 cell의 contentConfiguration에 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드와 차이점은 UIImageView, UILabel을 직접 건드리지 않고, configuration 자체에서 설정한다는 차이점이 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 좋은 점은 위 코드는 테이블뷰 셀이었는데요!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMoNgS/btrYSCtxV0m/7zdC5kCXl3rBCWUSHjqXr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMoNgS/btrYSCtxV0m/7zdC5kCXl3rBCWUSHjqXr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMoNgS/btrYSCtxV0m/7zdC5kCXl3rBCWUSHjqXr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMoNgS%2FbtrYSCtxV0m%2F7zdC5kCXl3rBCWUSHjqXr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1188&quot; height=&quot;408&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 configuration을 컬렉션뷰 셀에도 적용하면 그대로 사용할 수 있다고 합니다. 더 정확하게는 content Configuration을 지원하는 모든 뷰에서 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 가능한 이유는 configuration을 compasable(구성)할 수 있기 때문이라고 하는데, 좀 더 풀어쓰면 기존 방식과 같이 셀 클래스에서 모양을 구성하는 대신, configuration을 만들고 이걸 지원하는 셀이나 뷰에 필요한 것들을 연결하여 사용하다 보니 가능하다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간 짝 맞추기 게임 느낌으로 이해하면 될 거 같아요. 근데 커스텀 셀과 같이 복잡한 셀의 경우엔 어떻게 하려나? 하는 의문이 들긴 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의문을 뒤로하고 일단 영상에서는 configuration 쓰면 좋은 점을 막 자랑을 해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cADi3l/btrYTNBkKdb/a2BMPpqHkd7gm7vxMqkPj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cADi3l/btrYTNBkKdb/a2BMPpqHkd7gm7vxMqkPj1/img.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;610&quot; data-is-animation=&quot;false&quot; width=&quot;518&quot; height=&quot;396&quot; data-widthpercent=&quot;50.85&quot; style=&quot;width: 50.255715%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cADi3l/btrYTNBkKdb/a2BMPpqHkd7gm7vxMqkPj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcADi3l%2FbtrYTNBkKdb%2Fa2BMPpqHkd7gm7vxMqkPj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2EMI8/btrYVgwbjps/bcLrSTNwB8hxIJWc2N6pP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2EMI8/btrYVgwbjps/bcLrSTNwB8hxIJWc2N6pP0/img.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;650&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.15&quot; style=&quot;width: 48.581494%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2EMI8/btrYVgwbjps/bcLrSTNwB8hxIJWc2N6pP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2EMI8%2FbtrYVgwbjps%2FbcLrSTNwB8hxIJWc2N6pP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxrlwO/btrYYotNplQ/AkMhmNeHbPNbKUt8GUQjyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxrlwO/btrYYotNplQ/AkMhmNeHbPNbKUt8GUQjyK/img.png&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;638&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.87&quot; style=&quot;width: 49.290981%; margin-right: 10px; margin-top: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxrlwO/btrYYotNplQ/AkMhmNeHbPNbKUt8GUQjyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxrlwO%2FbtrYYotNplQ%2FAkMhmNeHbPNbKUt8GUQjyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIc9ZC/btrYRQMCNAA/VLkRtPAQGlhmOSpDKFHeeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIc9ZC/btrYRQMCNAA/VLkRtPAQGlhmOSpDKFHeeK/img.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;630&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.13&quot; style=&quot;width: 49.546228%; margin-top: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIc9ZC/btrYRQMCNAA/VLkRtPAQGlhmOSpDKFHeeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIc9ZC%2FbtrYRQMCNAA%2FVLkRtPAQGlhmOSpDKFHeeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터치하고 있을 때 셀이 투명해지는 인터랙션, 터치해서 선택됐을 때 셀이 강조되는 것을 보여주고, 여러 상태를 넣었을 때 화면도 보여줍니다. 이렇게 상태에 따라 다르게 보이는 것을 configuration을 사용하여 셀을 설정하면 자동으로 처리할 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bymduD/btrYSErl9yf/UZ1AVzmTXoEN7bWBdYSGJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bymduD/btrYSErl9yf/UZ1AVzmTXoEN7bWBdYSGJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bymduD/btrYSErl9yf/UZ1AVzmTXoEN7bWBdYSGJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbymduD%2FbtrYSErl9yf%2FUZ1AVzmTXoEN7bWBdYSGJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;216&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;216&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;configuration은 뷰(셀)의 특정 상태에 대한 모양을 설명하는 것이라고 보면 된다고 합니다. 그래서 뷰(셀)에 적용하기 전까지는 아무런 작업도 하지 않는다고 해요. 그리고 아까 말한 대로 configuration은 이를 지원하는 모든 뷰(셀)에서 재사용이 가능하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이러한 configuration에는 어떤 것이 있느냐 하면!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bml03S/btrYT86kxNf/8iokuZyqToRqZfySrXK1h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bml03S/btrYT86kxNf/8iokuZyqToRqZfySrXK1h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bml03S/btrYT86kxNf/8iokuZyqToRqZfySrXK1h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbml03S%2FbtrYT86kxNf%2F8iokuZyqToRqZfySrXK1h0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;664&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 background, list content라고 하는 두 개의 configuration이 있다고 합니다. 각각 어떤 역할을 하는지 살펴볼게요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz8afj/btrYRv9QoHe/IGpDFPI535FEURs65QAbg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz8afj/btrYRv9QoHe/IGpDFPI535FEURs65QAbg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz8afj/btrYRv9QoHe/IGpDFPI535FEURs65QAbg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz8afj%2FbtrYRv9QoHe%2FIGpDFPI535FEURs65QAbg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1204&quot; height=&quot;664&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Background Configuration은 배경이 어떻게 보일 지를 결정하는 몇 개의 프로퍼티가 있습니다. 이 프로터티를 설정하는 간단한 코드들로 배경색, 블러처리와 같은 다양한 시각적 효과를 처리할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 복잡한 뷰를 위한 커스텀 기능을 추가할 수도 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YvDh0/btrYSpnC9Ko/Kqi36FGqvyMBNofbaFtMS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YvDh0/btrYSpnC9Ko/Kqi36FGqvyMBNofbaFtMS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YvDh0/btrYSpnC9Ko/Kqi36FGqvyMBNofbaFtMS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYvDh0%2FbtrYSpnC9Ko%2FKqi36FGqvyMBNofbaFtMS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1190&quot; height=&quot;670&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 List Content Configuration입니다. 프로퍼티를 보면 기존에 테이블뷰 셀 스타일과 비슷해 보이지만, 더 강력한 기능을 가지고 있다고 해요. 많은 텍스트를 표시할 수 있는 유연한 레이아웃과 접근성을 위해 큰 텍스트 사이즈를 지원하는 레이아웃도 제공한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘네 둘은 같이 쓰이는데 동일한 설계 원칙을 갖는다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VnxVi/btrYXkE6iiM/l93zU1KOVVP03zKGNuDUkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VnxVi/btrYXkE6iiM/l93zU1KOVVP03zKGNuDUkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VnxVi/btrYXkE6iiM/l93zU1KOVVP03zKGNuDUkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVnxVi%2FbtrYXkE6iiM%2Fl93zU1KOVVP03zKGNuDUkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1202&quot; height=&quot;544&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 원칙은 두 가지라고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Inexpensive to create&lt;/li&gt;
&lt;li&gt;Always start with a fresh configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;background, list content configuration 사용할 때 자원이 많이 들지 않는다고 합니다. 그리고 둘 다 값 타입이라 변경 사항을 다시 셀에 설정하기 전까지는 아무런 영향도 주지 않는다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 셀을 구성할 땐 defaultContentConfiguration()로 새로운 configuration을 매번 만들어야 하는데, 비용이 저렴하니 매번 만드는 코드에 대한 걱정을 하지 말라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nk9pd/btrYThvTGJN/JkyUl3NxaBzFyanM77IuQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nk9pd/btrYThvTGJN/JkyUl3NxaBzFyanM77IuQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nk9pd/btrYThvTGJN/JkyUl3NxaBzFyanM77IuQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNk9pd%2FbtrYThvTGJN%2FJkyUl3NxaBzFyanM77IuQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;166&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Configuration을 사용할 땐 이전 상태에 대해 전혀 생각할 필요가 없기 때문에, 이전 configuration을 가지고 뭔갈 하려고 하지 말라고 합니다. 즉 아까도 언급한 대로 매번 새로운 configuration을 만들어서 설정한 뒤 셀에 적용하라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든 configuration을 셀에 적용하면 UIKit이 알아서 내부적으로 뭐가 바뀌었는지 파악하고 뷰를 효율적으로 업데이트한다고 하네요. 아주 좋네요 :)&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;580&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnIiiL/btrYRQFO0JZ/b4vEzBBhQi1kJOHoUl0g3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnIiiL/btrYRQFO0JZ/b4vEzBBhQi1kJOHoUl0g3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnIiiL/btrYRQFO0JZ/b4vEzBBhQi1kJOHoUl0g3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnIiiL%2FbtrYRQFO0JZ%2Fb4vEzBBhQi1kJOHoUl0g3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;244&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 configuration은 상태별 처리를 잘해주며, 애니메이션 처리도 애니메이션 내부에서 configuration을 설정해주기만 하면 된다고 하네요. 그리고 매번 새로운 configuration을 적용해 주기 때문에, 언제나 현재 적용된 것만 신경 쓰면 됩니다! 그리고 configuration은 성능도 좋다고 합니다. 부드러운 스크롤을 원한다면 적용해 보라고 하네요.&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;Configuration State&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으로 configuration state를 알아봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw2S3u/btrYTp8uHr2/zKhP1LIPh7rlktbEQpHpik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw2S3u/btrYTp8uHr2/zKhP1LIPh7rlktbEQpHpik/img.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;656&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;33.41&quot; style=&quot;width: 32.637737%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw2S3u/btrYTp8uHr2/zKhP1LIPh7rlktbEQpHpik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw2S3u%2FbtrYTp8uHr2%2FzKhP1LIPh7rlktbEQpHpik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCgrpI/btrYTgw0zQs/dbUyfWU9EFfEfMmljUstc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCgrpI/btrYTgw0zQs/dbUyfWU9EFfEfMmljUstc1/img.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;644&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;34.04&quot; style=&quot;width: 33.245894%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCgrpI/btrYTgw0zQs/dbUyfWU9EFfEfMmljUstc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCgrpI%2FbtrYTgw0zQs%2FdbUyfWU9EFfEfMmljUstc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buzVAO/btrYTOf18My/RvDRunnCnYdk5FVQbEXzkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buzVAO/btrYTOf18My/RvDRunnCnYdk5FVQbEXzkK/img.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;716&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;32.55&quot; style=&quot;width: 31.790788%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buzVAO/btrYTOf18My/RvDRunnCnYdk5FVQbEXzkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuzVAO%2FbtrYTOf18My%2FRvDRunnCnYdk5FVQbEXzkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1246&quot; height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에는 위와 같이 정말 많은 상태들이 있습니다. 이러한 모든 상태들이 configuration state를 결정하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsGYCt/btrYT8L2MyR/bKWEFDHcpoFcmsPBrpg8Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsGYCt/btrYT8L2MyR/bKWEFDHcpoFcmsPBrpg8Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsGYCt/btrYT8L2MyR/bKWEFDHcpoFcmsPBrpg8Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsGYCt%2FbtrYT8L2MyR%2FbKWEFDHcpoFcmsPBrpg8Y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;491&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 configuration state는 어디에 있는가 하면, 위와 같이 셀과 header, footer에는 자신만의 configuration state가 있습니다. 그럼 view configuration은 어떻게 생겼을지 볼게요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AKG3e/btrYTg4UC9m/kWRKk0aLihkVlPgHpRudr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AKG3e/btrYTg4UC9m/kWRKk0aLihkVlPgHpRudr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AKG3e/btrYTg4UC9m/kWRKk0aLihkVlPgHpRudr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAKG3e%2FbtrYTg4UC9m%2FkWRKk0aLihkVlPgHpRudr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;582&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Trait Collection을 시작으로 highlighted, selected, disabled, focused라는 네 가지 상태가 있고, 있을 수도 있고 없을 수도 있지만 직접 만든 custom State도 있습니다. 간단하죠?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3PvMr/btrYRU2tl5d/kFSKsHdiF2LPArtS1Tt3gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3PvMr/btrYRU2tl5d/kFSKsHdiF2LPArtS1Tt3gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3PvMr/btrYRU2tl5d/kFSKsHdiF2LPArtS1Tt3gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3PvMr%2FbtrYRU2tl5d%2FkFSKsHdiF2LPArtS1Tt3gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;652&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 cell configuration state를 보면 위와 같습니다. view configuration state가 가지고 있던 모든 것을 가지고 있고, 추가로 editing, swiped, expanded, drag and drop이라는 상태들도 가지고 있네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbcuXa/btrYSkzT5Yj/sSdiJds9FSAtE4x6gNZ2O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbcuXa/btrYSkzT5Yj/sSdiJds9FSAtE4x6gNZ2O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbcuXa/btrYSkzT5Yj/sSdiJds9FSAtE4x6gNZ2O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbcuXa%2FbtrYSkzT5Yj%2FsSdiJds9FSAtE4x6gNZ2O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1298&quot; height=&quot;714&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이러한 configuration state로 할 수 있는 게 무엇이냐 하면, 위와 같이 상태에 대해서 configuration을 업데이트하는 것입니다. 즉 configuration에 대해 state를 넣어주면, 해당 state의 configuration이 반환되어 그걸 사용하면 되는 거죠. 당연히 값 타입이니 기존 것과 새로 만들어진 것은 독립적이므로 서로 영향을 주지 않습니다.&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;794&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zi7zg/btrYT95gqOM/I9ZuH3t4SLARkrKk8AXMwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zi7zg/btrYT95gqOM/I9ZuH3t4SLARkrKk8AXMwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zi7zg/btrYT95gqOM/I9ZuH3t4SLARkrKk8AXMwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzi7zg%2FbtrYT95gqOM%2FI9ZuH3t4SLARkrKk8AXMwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;530&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 예제로 봤던 화면을 다시 가져와보면, 다양한 상태에 따라 모양이 다른 것을 볼 수 있는데요, 이걸 자동으로 되는 이유가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Br7QS/btrYXje74R4/FUKHMVf7MesH64qlI6NM60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Br7QS/btrYXje74R4/FUKHMVf7MesH64qlI6NM60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Br7QS/btrYXje74R4/FUKHMVf7MesH64qlI6NM60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBr7QS%2FbtrYXje74R4%2FFUKHMVf7MesH64qlI6NM60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1244&quot; height=&quot;412&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 sutomatic configuration update라는 기능이 있기 때문입니다.&lt;/p&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라서 background, content configuration은 configuration state가 바뀔 때마다 configuration을 해당 상태에 맞게 업데이트한다고 합니다. 물론 false로 바꾸면 업데이트되지 않겠죠?&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;1152&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDh2LV/btrYT9YtRav/8mmcU2neZQTJNtieHSBac1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDh2LV/btrYT9YtRav/8mmcU2neZQTJNtieHSBac1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDh2LV/btrYT9YtRav/8mmcU2neZQTJNtieHSBac1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDh2LV%2FbtrYT9YtRav%2F8mmcU2neZQTJNtieHSBac1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1152&quot; height=&quot;320&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이러한 configuration update를 위한 코드는 어디에 둬야 할까요? 정답은 바로 위와 같이 애플에서 만들어 둔 updateConfiguration 메서드를 오버라이드해서 사용해면 된다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H7vBL/btrYSi9YDBS/QH0M5WoYKCEdeoT8TMIeK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H7vBL/btrYSi9YDBS/QH0M5WoYKCEdeoT8TMIeK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H7vBL/btrYSi9YDBS/QH0M5WoYKCEdeoT8TMIeK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH7vBL%2FbtrYSi9YDBS%2FQH0M5WoYKCEdeoT8TMIeK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;194&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;updateConfiguration(using state:) 메서드는 셀이 처음 보이기 전에 항상 호출되고, configuration state가 변경될 때마다 다시 호출되어 state에 맞는 새로운 configuration을 설정할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까도 언급됐지만 configuration을 사용할 때는 이전 상태를 걱정할 필요 없이 항상 새로운 configuration을 만들어서 설정한 뒤 셀에 적용하면 됩니다. 그리고 configuration을 설정하고 적용하는 코드를 한 곳에 두는 것이 좋다고 합니다. 보통은 그 위치가 updateConfiguration(using state:) 메서드라고 하네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 이유에서건 셀을 다시 구성해야 한다면 setNeedsUpdateConfiguration()을 호출하여 업데이트하면 된다고 합니다. (그러니까 실제 업데이트는 setNeedsUpdateConfiguration()을 호출해서 처리하면 된다는 뜻 같네요)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzcnYm/btrYSrlsL0L/5oHdF1KWnBWQh9AabdcZNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzcnYm/btrYSrlsL0L/5oHdF1KWnBWQh9AabdcZNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzcnYm/btrYSrlsL0L/5oHdF1KWnBWQh9AabdcZNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzcnYm%2FbtrYSrlsL0L%2F5oHdF1KWnBWQh9AabdcZNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1184&quot; height=&quot;590&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 updateConfiguration 메서드에 대한 예제를 한 번 볼게요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 default configuration을 가지고 와서 현재 state로 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 셀이 표시하는 항목에 대한 값들을 설정하면 됩니다. 위 코드에서는 highlighted, selected 상태라면 색상을 다르게 보여주도록 설정하고 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 그렇게 설정된 새로운 configuration을 셀에 다시 적용하면 끝입니다.&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;위 예제에서는 색상을 특정 상태에 대해서 다르게 설정해 줬는데요, Color Transformer라는 새로운 타입을 사용하여 상태에 따라 색상을 변경할 수도 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GDTFq/btrYRvIJqEX/zaDKDYmoMpKn6Ncgx6WZhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GDTFq/btrYRvIJqEX/zaDKDYmoMpKn6Ncgx6WZhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GDTFq/btrYRvIJqEX/zaDKDYmoMpKn6Ncgx6WZhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGDTFq%2FbtrYRvIJqEX%2FzaDKDYmoMpKn6Ncgx6WZhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1258&quot; height=&quot;674&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Color Transformer는 한 가지 색을 가져와서 어떤 식으로든 원래 색을 수정하여 다른 색을 반환한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 색의 회색조 버전을 반환하는 color transformer가 있을 수 있는 거죠. Color Transformer는 단순한 기능인데, 애플이 늘 새로운 기능에 대해 하는 말대로 강력하다고 합니다. 얘가 강력한 이유는 다른 color transformer를 사용해서 동일한 input color에 대해 다양한 색을 생성할 수 있기 때문이라고 합니다. (위 그림처럼요!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 얘를 이용해서 상태에 따라 어떤 것을 할 수 있느냐 하면, 특정 상태의&amp;nbsp;default Configuration에는 color transformer가 미리 설정되어 있기도 하다고 합니다. 그래서 뭐 이걸 직접 설정하면 아까와 같이 상태에 따른 색을 매번 지정하지 않고, 미리 상태에 따른 색을 지정해 둔 뒤 사용할 수 있을 거 같아요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Background and Content Configuration&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 마지막 섹션인 Background and Content Configuration입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 configuration이 다양한 상태에 대한 업데이트 하는 방법에 대해 알아봤는데요, 이번 섹션에서는 background, content Configuration을 사용할 때 알아야 할 세부 정보를 알려준다고 합니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0XxI7/btrYT9q7IP3/TNjpcuH0ucqABjIjraRzl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0XxI7/btrYT9q7IP3/TNjpcuH0ucqABjIjraRzl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0XxI7/btrYT9q7IP3/TNjpcuH0ucqABjIjraRzl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0XxI7%2FbtrYT9q7IP3%2FTNjpcuH0ucqABjIjraRzl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1184&quot; height=&quot;614&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 뷰, 테이블 뷰의 셀, header, footer의 background configuration의 경우 기본값으로 알아서 설정된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의 깊게 봐야 할 것은 content configuration인데요, 위에 그림에도 나와있고 아까부터 예제에서 계속 등장하던 defaultContentConfiguration() 메서드를 사용하여 configuration을 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 원하는 스타일이 따로 있다면, 위 그림에서와 같이 UIBackgroundConfiguration, UIListContentConfiguration의 타입 메서드에서 스타일에 맞는 configuration을 가져올 수도 있다고 합니다. 위 그림에서는 sidebarcell 스타일에 대한 configuration을 가져오는 것을 볼 수 있는데, 다른 스타일들도 모두 가져올 수 있다고 해요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciOZjV/btrYUuPrHwZ/kHqb5NmMSJ4gdrllxjjJZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciOZjV/btrYUuPrHwZ/kHqb5NmMSJ4gdrllxjjJZk/img.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;618&quot; data-is-animation=&quot;false&quot; width=&quot;730&quot; height=&quot;564&quot; data-widthpercent=&quot;49.62&quot; style=&quot;width: 49.047213%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciOZjV/btrYUuPrHwZ/kHqb5NmMSJ4gdrllxjjJZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciOZjV%2FbtrYUuPrHwZ%2FkHqb5NmMSJ4gdrllxjjJZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brEBjY/btrYVhoKFgU/hkOeADqiVIXKWlB402EcTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brEBjY/btrYVhoKFgU/hkOeADqiVIXKWlB402EcTk/img.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;624&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.38&quot; style=&quot;width: 49.789996%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brEBjY/btrYVhoKFgU/hkOeADqiVIXKWlB402EcTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrEBjY%2FbtrYVhoKFgU%2FhkOeADqiVIXKWlB402EcTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 sidebar 스타일로 셀을 만든 화면을 보면 위와 같습니다. 그리고 상태에 따라 background와 content 모양도 바뀌는 것을 볼 수 있어요.&lt;/p&gt;
&lt;p 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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEH5Ym/btrYYp0Y4Dz/DG7BkuW5TGd6jLX4iX3MK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEH5Ym/btrYYp0Y4Dz/DG7BkuW5TGd6jLX4iX3MK0/img.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;634&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;32.93&quot; style=&quot;width: 32.160362%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEH5Ym/btrYYp0Y4Dz/DG7BkuW5TGd6jLX4iX3MK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEH5Ym%2FbtrYYp0Y4Dz%2FDG7BkuW5TGd6jLX4iX3MK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pt5IV/btrY1mJHlUR/Pkmy6YX7e21Fw21twTBcP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pt5IV/btrY1mJHlUR/Pkmy6YX7e21Fw21twTBcP1/img.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;626&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;33.35&quot; style=&quot;width: 32.571357%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pt5IV/btrY1mJHlUR/Pkmy6YX7e21Fw21twTBcP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPt5IV%2FbtrY1mJHlUR%2FPkmy6YX7e21Fw21twTBcP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU5QQZ/btrYXjs5XO3/vzWSVWKPHLhe3jTakh4Jrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU5QQZ/btrYXjs5XO3/vzWSVWKPHLhe3jTakh4Jrk/img.png&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;622&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;33.72&quot; style=&quot;width: 32.9427%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU5QQZ/btrYXjs5XO3/vzWSVWKPHLhe3jTakh4Jrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU5QQZ%2FbtrYXjs5XO3%2FvzWSVWKPHLhe3jTakh4Jrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;814&quot; height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽부터 차례대로 grouped, inset grouped, plain 스타일입니다. 이 스타일들이 익숙하다 했더니 테이블 뷰에서 봤던 스타일들이네요.&lt;/p&gt;
&lt;p 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B1wdq/btrYSkAlexE/kXK9DRl3HJXY5lQSil8Vt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B1wdq/btrYSkAlexE/kXK9DRl3HJXY5lQSil8Vt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B1wdq/btrYSkAlexE/kXK9DRl3HJXY5lQSil8Vt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB1wdq%2FbtrYSkAlexE%2FkXK9DRl3HJXY5lQSil8Vt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;556&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 늘어난 텍스트 크기에 따라 레이아웃이 바뀌는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Content Configuration은 처음부터 이러한 동적 레이아웃을 지원하도록 만들어졌다고 하네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이게 어떻게 작동하는 건지 궁금했는데 영상에서 바로 알려줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KJ0DX/btrYSqmZTDE/Ktw0lRfvuME7F3UsLQUuhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KJ0DX/btrYSqmZTDE/Ktw0lRfvuME7F3UsLQUuhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KJ0DX/btrYSqmZTDE/Ktw0lRfvuME7F3UsLQUuhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKJ0DX%2FbtrYSqmZTDE%2FKtw0lRfvuME7F3UsLQUuhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;638&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에 나온 레이아웃을 보면 파란색으로 표시된 상하좌우 margin이 있고, 주황색으로 표시된 padding 속성을 볼 수 있는데, 이런 것들을 &lt;span&gt;Content Configuration을 활용하면&lt;span&gt;&amp;nbsp;제어할 수 있다고 합니다. 이렇게 제어한 값들도 셀의 높이를 계산하는데 당연히 영향을 준다고 합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFyZMS/btrYUwfqQbU/FvgtfRKe4IeAPa3cxS9j6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFyZMS/btrYUwfqQbU/FvgtfRKe4IeAPa3cxS9j6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFyZMS/btrYUwfqQbU/FvgtfRKe4IeAPa3cxS9j6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFyZMS%2FbtrYUwfqQbU%2FFvgtfRKe4IeAPa3cxS9j6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;622&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 설명을 해준다면서 위 예제를 보여줍니다. 각각의 셀을 보면 이미지와 텍스트가 있는 간단한 셀이네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djgcNy/btrZdeY09qW/bKcw6hJ9hTfL8BJx6kF7gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djgcNy/btrZdeY09qW/bKcw6hJ9hTfL8BJx6kF7gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djgcNy/btrZdeY09qW/bKcw6hJ9hTfL8BJx6kF7gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjgcNy%2FbtrZdeY09qW%2FbKcw6hJ9hTfL8BJx6kF7gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1174&quot; height=&quot;610&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이미지 크기가 달라지면 위와 같이 못생겨 보이게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8A0L6/btrZei7WUX6/ZEn7v5DEbVzed6Vs6sU4mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8A0L6/btrZei7WUX6/ZEn7v5DEbVzed6Vs6sU4mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8A0L6/btrZei7WUX6/ZEn7v5DEbVzed6Vs6sU4mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8A0L6%2FbtrZei7WUX6%2FZEn7v5DEbVzed6Vs6sU4mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1158&quot; height=&quot;622&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 위 그림처럼 이미지가 정렬되고, 텍스트는 이미지의 trailing에 대하여 padding 값을 가지기 때문이라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같은 셀을 원하는 대로 보이게 하려면, 각각의 이미지에 대한 reserved layout size를 설정해줘야 한다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2DY2I/btrZa3jUupW/DaaaFsKg9BRiH1UEgccWk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2DY2I/btrZa3jUupW/DaaaFsKg9BRiH1UEgccWk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2DY2I/btrZa3jUupW/DaaaFsKg9BRiH1UEgccWk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2DY2I%2FbtrZa3jUupW%2FDaaaFsKg9BRiH1UEgccWk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1112&quot; height=&quot;628&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 reserved layout size가 설정된 화면이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨간색 점선이 이미지에 대한 reserved layout size라고 합니다. reserved layout size는 이미지의 실제 크기에 영향을 주지도 않는데, 실제로 위와 같이 reserved layout size보다 이미지 사이즈가 클 경우 확장되어 보이는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트의 경우 reserved layout size을 기준으로 배치되기 때문에 잘 보이는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마지막으로 Configuration을 사용할 계획이라면 참고해야 할 것들을 알려준다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pP9is/btrZewkNRXw/XFI9VgHyw7BGyDLaTyYb70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pP9is/btrZewkNRXw/XFI9VgHyw7BGyDLaTyYb70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pP9is/btrZewkNRXw/XFI9VgHyw7BGyDLaTyYb70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpP9is%2FbtrZewkNRXw%2FXFI9VgHyw7BGyDLaTyYb70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;536&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 사용하던 셀이 있는 경우에 configuration이 기존에 사용하던 값들과 상호배타적이라는 점을 기억하라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Background configuration를 사용하게 되면 backgroundColor, backgroundView가 nil로 리셋된다고 합니다. (반대의 경우도 마찬가지라고 하네요.) 따라서 기존에 사용하던 background 관련 코드들과 background configuration을 함께 사용하지 않도록 하라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테이블뷰 셀에 한해서, content configuration을 사용하면 imageView, textLabel, detailTextLabel과 같은 기본으로 제공하는 뷰들을 대체한다고 합니다. 또한 이후 버전에서는 더 이상 사용되지 않을 거니 주의하라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5ARHk/btrZclqxUiT/nlQMe5AVGDbOdrpk6MJEk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5ARHk/btrZclqxUiT/nlQMe5AVGDbOdrpk6MJEk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5ARHk/btrZclqxUiT/nlQMe5AVGDbOdrpk6MJEk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5ARHk%2FbtrZclqxUiT%2FnlQMe5AVGDbOdrpk6MJEk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;404&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 많은 커스텀 작업을 하고 싶을 때 configuration을 사용하면 더 많은 옵션을 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List content configuration의 경우 모든 렌더링을 구현하는 list content view도 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;configuration을 사용해서 list content view를 생성하고 업데이트 한 뒤 직접 만든 뷰의 subview로 추가할 수 있다고 하네요. 이렇게 하면 content configuration의 모든 기능을 활용가능하고, 직접 만든 뷰와 결합하여 사용 가능하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List content view는 그냥 UIView이므로 테이블 뷰, 컬렉션 뷰가 아닌 어디서나 사용가능하다고 해요.&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;918&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckuvNd/btrY1mEO2if/CIf5KS0DSmkGuzLLzNbtTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckuvNd/btrY1mEO2if/CIf5KS0DSmkGuzLLzNbtTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckuvNd/btrY1mEO2if/CIf5KS0DSmkGuzLLzNbtTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckuvNd%2FbtrY1mEO2if%2FCIf5KS0DSmkGuzLLzNbtTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;918&quot; height=&quot;94&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것을 직접 만들고 싶다면 어떻게 해야 할까요? 그런 경우에도 configuration의 도움을 받을 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;configuration은 성능이 좋기 때문에 위에 나온 문구대로 커스텀뷰를 만들더라도 default configuration을 만들어서 어느 정도 도움을 받는 것도 추천한다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQIWIt/btrZa3qHo1I/o77hhuGJYNIaYJa8oJMIk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQIWIt/btrZa3qHo1I/o77hhuGJYNIaYJa8oJMIk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQIWIt/btrZa3qHo1I/o77hhuGJYNIaYJa8oJMIk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQIWIt%2FbtrZa3qHo1I%2Fo77hhuGJYNIaYJa8oJMIk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;84&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가서는 content configuration, content 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;즉 어떤 복잡한 뷰에서도 configuration의 도움을 받을 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxJenm/btrY2njwMJG/05KHRbPOXZeLIAD4GfkOg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxJenm/btrY2njwMJG/05KHRbPOXZeLIAD4GfkOg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxJenm/btrY2njwMJG/05KHRbPOXZeLIAD4GfkOg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxJenm%2FbtrY2njwMJG%2F05KHRbPOXZeLIAD4GfkOg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;248&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이렇게 쓰기 쉽고, 강력한 기능을 가지고 있지만 매우 가벼운, 그리고 다양한 상태를 알아서 업데이트해주는 configuration을 잘 써달라고 하며 영상이 끝이 납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실한 건 실제로 써봐야 알겠지만, 일단 이번 영상에서 알려준 내용만 보면 쓰기 쉬워 보이네요.&lt;/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;</description>
      <category>Apple/WWDC 2020</category>
      <category>uicollectionview</category>
      <category>UITableView</category>
      <category>WWDC2020</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/292</guid>
      <comments>https://icksw.tistory.com/292#entry292comment</comments>
      <pubDate>Mon, 13 Feb 2023 21:05:24 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2020] Lists in UICollectionView</title>
      <link>https://icksw.tistory.com/291</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #666666;&quot;&gt;안녕하세요 Pingu입니다. &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;background-color: #ffffff; color: #666666;&quot;&gt;오늘은 WWDC 2020의 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10026/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Lists in UICollectionView&lt;/a&gt;라는 영상을 보고 정리한 글을 써보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;iOS 14부터 이걸 사용할 수 있는데 마침 이 기능을 사용할 일이 있어서 공부를 하게 되었네요.&amp;nbsp;&lt;span style=&quot;caret-color: #666666; background-color: #ffffff;&quot;&gt;영상을 보고 느낀 점을 매우 간략하게 요약하면 &quot;우리가 테이블뷰 기능을 컬렉션뷰에서도 쓸 수 있게 만들었다!&quot;입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span style=&quot;caret-color: #666666; background-color: #ffffff;&quot;&gt;Lists&amp;nbsp;in&amp;nbsp;UICollectionView&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BkJKC/btrXOuDbsOC/TllK1kymUWb74YQpN7ig00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BkJKC/btrXOuDbsOC/TllK1kymUWb74YQpN7ig00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BkJKC/btrXOuDbsOC/TllK1kymUWb74YQpN7ig00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBkJKC%2FbtrXOuDbsOC%2FTllK1kymUWb74YQpN7ig00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;612&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요게 이제 컬렉션 뷰 설정에 대한 간략한 아키텍처라고 하는데 이번 영상에서는 오른쪽 위에 있는 &quot;List Cell&quot;, &quot;View Configuaration&quot;에 대해서 알아본다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 전체적으로 공부하고 싶다면 아래 영상을 보라고 하네용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10097/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Advances in Collection View&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 어쨌든 이번 영상에서는 &quot;List Cell&quot;, &quot;View Configuaration&quot; 얘네 둘만 집중적으로 다룬다고 합니다.&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;UICollectionView의 List가 뭐죠?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq8jbE/btrXN30ollu/aG9ky8EUIjQULtGR4WbwU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq8jbE/btrXN30ollu/aG9ky8EUIjQULtGR4WbwU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq8jbE/btrXN30ollu/aG9ky8EUIjQULtGR4WbwU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq8jbE%2FbtrXN30ollu%2FaG9ky8EUIjQULtGR4WbwU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;382&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 목록이 이번 영상에서 알아보는 CollectionView에서의 List를 요약한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 보자면, iOS 14부터 UICollectionView도 UITableView처럼 사용할 수 있게 됐는데, 애플은 이걸 iOS 13에 만들어둔 Compositional Layout을 활용해서 구현했다고 하네요. 또한 커스텀이 자유롭다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 제일 좋았던 건 &quot;Optimized self sizing&quot;인데, 원래 컬렉션뷰는 높이 계산을 늘 해줘야 해서 약간 귀찮은..적도 있었는데 이걸 auto layout 기능을 통해 컬렉션뷰가 알아서 처리해 주는 기능도 있다고 합니다. 만약 직접 높이를 계산해줘야 한다고 한다면 preferredLayoutAttributesFittingAttributes 메서드를 override 해서 사용하면 된다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmOZap/btrXPwHcYaQ/pPt1Ezm5oQK49odBejpp41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmOZap/btrXPwHcYaQ/pPt1Ezm5oQK49odBejpp41/img.png&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;714&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;52.51&quot; style=&quot;width: 51.901728%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmOZap/btrXPwHcYaQ/pPt1Ezm5oQK49odBejpp41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmOZap%2FbtrXPwHcYaQ%2FpPt1Ezm5oQK49odBejpp41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G6QJ5/btrXMTKs95d/D2bIOCbsAujWh5Xc61PM0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G6QJ5/btrXMTKs95d/D2bIOCbsAujWh5Xc61PM0k/img.png&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;696&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;47.49&quot; style=&quot;width: 46.935481%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G6QJ5/btrXMTKs95d/D2bIOCbsAujWh5Xc61PM0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG6QJ5%2FbtrXMTKs95d%2FD2bIOCbsAujWh5Xc61PM0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;696&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 List를 사용하면 이렇게 자유롭게 커스터마이징 할 수 있다고 자랑합니다.&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;UICollectionView List의 Components&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GC7n4/btrXPxF6gc6/dkZ6LEeNdD16uwLsHAIyD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GC7n4/btrXPxF6gc6/dkZ6LEeNdD16uwLsHAIyD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GC7n4/btrXPxF6gc6/dkZ6LEeNdD16uwLsHAIyD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGC7n4%2FbtrXPxF6gc6%2FdkZ6LEeNdD16uwLsHAIyD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;478&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와 같이 아주 좋은 List는 어떻게 구성되어 있을지 알아보겠습니다.&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;UICollectionLayoutListConfiguration
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS14에 새로 추가된 타입으로 컬렉션뷰에서 list를 만들 때 레이아웃을 담당하는 녀석이라고 보면 됩니다.&lt;/li&gt;
&lt;li&gt;얘는 위 그림에서 그 아래에 있는 iOS13에 추가된 NSCollectionLayoutSection, UICollectionViewCompositionalLayout을 기반으로 만들어졌다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U7ljG/btrXM3TV0i2/6jIvXWP1JIClk7PhTE9cJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U7ljG/btrXM3TV0i2/6jIvXWP1JIClk7PhTE9cJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U7ljG/btrXM3TV0i2/6jIvXWP1JIClk7PhTE9cJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU7ljG%2FbtrXM3TV0i2%2F6jIvXWP1JIClk7PhTE9cJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1146&quot; height=&quot;638&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상에서는 UICollectionLayoutListConfiguration만 자세히 알아볼 건데요,&amp;nbsp;NSCollectionLayoutSection, UICollectionViewCompositionalLayout를 잘 모른다면 아래 영상을 보는 게 좋다고 합니다. (저는 잘 모르니 나중에 봐야겠네요 )&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/215/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Advances in Collection View Layout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;List Configuration&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼&amp;nbsp;UICollectionLayoutListConfiguration을 자세히 알아보겠습니다.&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;1196&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2h93R/btrXQgD9mIu/y0P0uabJ5NpidU4vCTyaoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2h93R/btrXQgD9mIu/y0P0uabJ5NpidU4vCTyaoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2h93R/btrXQgD9mIu/y0P0uabJ5NpidU4vCTyaoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2h93R%2FbtrXQgD9mIu%2Fy0P0uabJ5NpidU4vCTyaoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1196&quot; height=&quot;630&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UICollectionLayoutListConfiguration은 위와 같이 4가지로 나눌 수 있는데요 하나씩 볼게요&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;Appearance&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블뷰에 있던 스타일인 plain, grouped, inset-grouped를 아시나요? 이런 애들이 컬렉션뷰에서도 사용가능하다고 합니다. 또한 두 개의 새로운 스타일이 있는데 sidebar, sidebar plain라고 합니다. 이걸 사용하면 위 그림처럼 멋진 iPad 앱을 만들 수 있다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 간단히 appearance를 사용하는 방법을 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AOpPy/btrXSwGsAnS/3PTQjU7l9SEw4ez1iSFsxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AOpPy/btrXSwGsAnS/3PTQjU7l9SEw4ez1iSFsxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AOpPy/btrXSwGsAnS/3PTQjU7l9SEw4ez1iSFsxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAOpPy%2FbtrXSwGsAnS%2F3PTQjU7l9SEw4ez1iSFsxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1078&quot; height=&quot;186&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list를 만드는 가장 쉬운 방법은 위와 같이 UICollectionLayoutListConfiguration를 appearane 값으로 만드는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 난 뒤 이렇게 만들어진 configuration으로 UICollectionViewCompositionalLayout를 만들면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 테이블뷰와 큰 차이가 없는데 per-section setup이라고 불리는 기능이 추가로 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ENaUF/btrXOtEfVjz/v9yqVrus1azovvQ23eKkv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ENaUF/btrXOtEfVjz/v9yqVrus1azovvQ23eKkv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ENaUF/btrXOtEfVjz/v9yqVrus1azovvQ23eKkv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FENaUF%2FbtrXOtEfVjz%2Fv9yqVrus1azovvQ23eKkv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;198&quot; data-origin-width=&quot;1050&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;per-section setup을 사용할 땐 UICollectionLayoutListConfiguration은 그대로 사용하지만 위 코드와 같이&amp;nbsp;UICollectionViewCompositionalLayout 대신&amp;nbsp;NSCollectionLayoutSection을 사용한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 이제 섹션별로 레이아웃을 정의할 수 있다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mgEYS/btrXPsLErDO/keIQboRXb3LWTvGkqmUeD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mgEYS/btrXPsLErDO/keIQboRXb3LWTvGkqmUeD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mgEYS/btrXPsLErDO/keIQboRXb3LWTvGkqmUeD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmgEYS%2FbtrXPsLErDO%2FkeIQboRXb3LWTvGkqmUeD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1148&quot; height=&quot;496&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같이 sectionIndex가 0일 땐 아까 언급된 iOS 13에 추가된 기능인 Compositional Layouy API를 활용해서 &lt;span&gt;다른 레이아웃을 반환할 수 있는 거죠!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Headers and Footers&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으론 Header, Footer에 대해 알아볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UITableView와 다르게 UICollectionView에서는 Header, Footer를 명시적으로 활성화해줘야 하는데 여기엔 두 가지 방법이 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MVTyV/btrXQfSK7uQ/OmBk2UiOfmk80uGgU4bR20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MVTyV/btrXQfSK7uQ/OmBk2UiOfmk80uGgU4bR20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MVTyV/btrXQfSK7uQ/OmBk2UiOfmk80uGgU4bR20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMVTyV%2FbtrXQfSK7uQ%2FOmBk2UiOfmk80uGgU4bR20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1208&quot; height=&quot;600&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방법은 위와 같이 supplementary 뷰를 등록해서 사용하는 거죠.&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;1238&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4Czmo/btrXM4egiz5/QGLaPsJ6KhlppBnCttLfDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4Czmo/btrXM4egiz5/QGLaPsJ6KhlppBnCttLfDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4Czmo/btrXM4egiz5/QGLaPsJ6KhlppBnCttLfDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4Czmo%2FbtrXM4egiz5%2FQGLaPsJ6KhlppBnCttLfDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;520&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용할 때 유의할 점은 headerMode에 nil을 주면 assert 된다는 것입니다. 그러니 위와 같이 none을 주도록 해야 한다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이건 뭐 기존이랑 비슷한 방식이니.. 별 거 없어서 두 번째 방법이 기대됐는데..&lt;/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;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DUz84/btrXPr62JQu/cSi9o39KHChQOKzu2cKabK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DUz84/btrXPr62JQu/cSi9o39KHChQOKzu2cKabK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DUz84/btrXPr62JQu/cSi9o39KHChQOKzu2cKabK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDUz84%2FbtrXPr62JQu%2FcSi9o39KHChQOKzu2cKabK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;632&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더에만 사용할 수 있는 방법으로 섹션의 첫 번째 셀을 헤더로 사용하는 방법이라고 합니다. 근데 이걸 firstItemInSection이라는 타입을 따로 줬네요. 가끔 꼼수로 이용해 먹던 방법이긴 한데 이게 아예 이렇게 기능으로 나오다니... 더 편하게 사용할 수 있겠어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 사용하는 건 계층적 데이터 구조와 새로운 section-snapshot api를 사용할 때 추천한다고 하네요. 이게 어떻게 작동하는지는 &lt;a href=&quot;https://icksw.tistory.com/270&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Advances in Diffable Data Source&lt;/a&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;List Cell&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금까지 이것들을 기존 UICollectionViewCell로 쓰느냐? 하면 그건 아니고 UICollectionViewListCell이란걸 만들어뒀다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ListCell은 아래와 같은 작업을 할 때 유용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Separators&lt;/li&gt;
&lt;li&gt;Indentation&lt;/li&gt;
&lt;li&gt;Swipe Actions&lt;/li&gt;
&lt;li&gt;Accessories&lt;/li&gt;
&lt;li&gt;Default Content Configurations&lt;/li&gt;
&lt;/ul&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;a href=&quot;https://icksw.tistory.com/292&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Modern cell configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 ListCell이 제공하는 기능들을 하나씩 살펴보도록 해보겠습니당&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Separators&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nKFHB/btrXUrtvVeJ/Ky6E9Sev2jr146C2kIcdGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nKFHB/btrXUrtvVeJ/Ky6E9Sev2jr146C2kIcdGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nKFHB/btrXUrtvVeJ/Ky6E9Sev2jr146C2kIcdGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnKFHB%2FbtrXUrtvVeJ%2FKy6E9Sev2jr146C2kIcdGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;516&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 나온 UI가 올바르지 않은 UI라고 합니다. 왜지..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플의 주장은 separator가 셀의 기본 콘텐츠와 정렬되어야 하는데 위 UI에서는 기본 콘텐츠가 Label이라는 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IAzfF/btrXYai9OfO/MVtuJ6yDk1WX3jqvhSkAA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IAzfF/btrXYai9OfO/MVtuJ6yDk1WX3jqvhSkAA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IAzfF/btrXYai9OfO/MVtuJ6yDk1WX3jqvhSkAA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIAzfF%2FbtrXYai9OfO%2FMVtuJ6yDk1WX3jqvhSkAA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1108&quot; height=&quot;520&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 그래가지고 이렇게 Separator를 Label에 정렬되도록 바꾸는 게 좋다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 TableView에서는 separator inset이라는 값으로 제공했다고 하네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NrdcG/btrXVrGmCYi/pTvLKtKZxdXIX40iJhiW20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NrdcG/btrXVrGmCYi/pTvLKtKZxdXIX40iJhiW20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NrdcG/btrXVrGmCYi/pTvLKtKZxdXIX40iJhiW20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNrdcG%2FbtrXVrGmCYi%2FpTvLKtKZxdXIX40iJhiW20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1164&quot; height=&quot;578&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이제 위 화면처럼 safe area, layout margin도 있고 폰트 사이즈나 이미지도 동적 사이즈로 사용하는 곳에서는 레이블의 크기가 달라질 수 있는데요, 따라서 레이블의 끝부분을 미리 알고 거기에 정렬하기엔 기존 기능이 적합하지 않다고 합니다.&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;1170&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbwBqh/btrXWKysNXZ/DPxm7vCBqrhi5LKEBCrok0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbwBqh/btrXWKysNXZ/DPxm7vCBqrhi5LKEBCrok0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbwBqh/btrXWKysNXZ/DPxm7vCBqrhi5LKEBCrok0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbwBqh%2FbtrXWKysNXZ%2FDPxm7vCBqrhi5LKEBCrok0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;548&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 separator layout guide라는 개념을 도입했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 셀의 레이아웃을 구성한 뒤 separtor의 시작점을 위와 같이 직접 처리해 주면 된다고 합니다. 간단하네요!&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;Swipe Actions&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 ListCell을 사용해 보려는 이유인 Swipe Action입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거는 원래 TableView에서 제공하던 기능이었는데 이제 ListCell에도 제공된다고 해요.&lt;/p&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에서와 마찬가지로 leading, trailing에 스와이프 액션을 줄 수가 있고 당연하게도 설정해 둔 섹션의 셀에만 동작한다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYyNAO/btrXVkm29dA/rXOOZyjGaVHAdDC7DcbI4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYyNAO/btrXVkm29dA/rXOOZyjGaVHAdDC7DcbI4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYyNAO/btrXVkm29dA/rXOOZyjGaVHAdDC7DcbI4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYyNAO%2FbtrXVkm29dA%2FrXOOZyjGaVHAdDC7DcbI4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1218&quot; height=&quot;634&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같이 코드를 작성하면 스와이프 액션을 만들어서 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmr89f/btrXYaKeKI8/nKDLW1FB6rKFAz7KSnr7Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmr89f/btrXYaKeKI8/nKDLW1FB6rKFAz7KSnr7Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmr89f/btrXYaKeKI8/nKDLW1FB6rKFAz7KSnr7Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcmr89f%2FbtrXYaKeKI8%2FnKDLW1FB6rKFAz7KSnr7Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1208&quot; height=&quot;506&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플은 위와 같이 스와이프 액션에 indexPath를 캡처하지 말라고 하는데요, 셀을 삽입, 삭제 등의 동작이 있는 경우 해당 값이 상수가 아니기 때문이라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNqjPv/btrXUqBmIs7/UIFj1gfzxnVhhAI25U3pYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNqjPv/btrXUqBmIs7/UIFj1gfzxnVhhAI25U3pYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNqjPv/btrXUqBmIs7/UIFj1gfzxnVhhAI25U3pYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNqjPv%2FbtrXUqBmIs7%2FUIFj1gfzxnVhhAI25U3pYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1196&quot; height=&quot;502&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 대신 위와 같이 identifier를 사용하는 게 좋다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 삽입, 삭제와 같은 작업이 없어서 indexPath가 변하지 않는 상황이라면.. 사용해도 되지 않을까 싶기도 합니다.&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;Accessories&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 accessories입니다. 이거도 UITableView에 있던 건데, 기존에는 trailing side에만 있었죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zeEfa/btrXXhQozhc/30q0nOnf7K26OQ3DtDfGn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zeEfa/btrXXhQozhc/30q0nOnf7K26OQ3DtDfGn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zeEfa/btrXXhQozhc/30q0nOnf7K26OQ3DtDfGn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzeEfa%2FbtrXXhQozhc%2F30q0nOnf7K26OQ3DtDfGn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;296&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 ListCell에서는 이걸 개선해서 Leading, Trailing 모두 accessory가 있을 수 있고 심지어 각 부분마다 여러 개의 accessory도 있을 수 있다고 합니다.&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;1196&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B4oRq/btrXVZWYYFy/w58ZStawKSUaUjSEoyDHP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B4oRq/btrXVZWYYFy/w58ZStawKSUaUjSEoyDHP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B4oRq/btrXVZWYYFy/w58ZStawKSUaUjSEoyDHP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB4oRq%2FbtrXVZWYYFy%2Fw58ZStawKSUaUjSEoyDHP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1196&quot; height=&quot;560&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 TableView에서는 accessory가 그냥 데코레이션 뷰와 같은 느낌이었다면 ListCell에서는 기능을 활성화할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위에 나와있는 Reorder accessory로 셀을 구성한 뒤 사용자가 이걸 누르면 컬렉션 뷰가 알아서 reordering mode가 된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 뭔갈 처리해 주는 느낌이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거도 잘 사용하려면 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://icksw.tistory.com/270&quot;&gt;Advances in Diffable Data Source&lt;/a&gt;&amp;nbsp;이 영상을 보라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clFBsO/btrXWKSK3Gu/364nvfvGVHk1kIds8CfQV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clFBsO/btrXWKSK3Gu/364nvfvGVHk1kIds8CfQV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clFBsO/btrXWKSK3Gu/364nvfvGVHk1kIds8CfQV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclFBsO%2FbtrXWKSK3Gu%2F364nvfvGVHk1kIds8CfQV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1230&quot; height=&quot;506&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이걸 어떻게 사용하느냐 하면 위와 같이 cell의 accessories 프로퍼티를 활용하여 아주 간단하게 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 재밌는 점은 disclosureIndicator는 leading accessory이고 delete는 trailing accessory라는 걸 시스템이 알고 있어서 자동으로 배치해 준다고 해요.&lt;/p&gt;
&lt;p 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;1202&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vw9Ve/btrXXgxbzOk/skiHgBYolKUi3HikKFXD21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vw9Ve/btrXXgxbzOk/skiHgBYolKUi3HikKFXD21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vw9Ve/btrXXgxbzOk/skiHgBYolKUi3HikKFXD21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvw9Ve%2FbtrXXgxbzOk%2FskiHgBYolKUi3HikKFXD21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1202&quot; height=&quot;476&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;476&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;p data-ke-size=&quot;size16&quot;&gt;여기까지가 영상에서 ListCell을 소개하는 부분이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래도 거의 안 쓰긴 했는데 이제 TableView는 정말 쓰지 않아도 되겠어요.&lt;/p&gt;
&lt;p 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;</description>
      <category>Apple/WWDC 2020</category>
      <category>CollectionView</category>
      <category>listcell</category>
      <category>WWDC2020</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/291</guid>
      <comments>https://icksw.tistory.com/291#entry291comment</comments>
      <pubDate>Fri, 3 Feb 2023 00:21:47 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Encoding and Decoding - Operator 공부 14</title>
      <link>https://icksw.tistory.com/290</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/289&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Controlling Timing으로 분류된 Publisher, Operator에 대해 알아봤습니다. 이름 그대로 전달받은 값을 시간에 관련해서 처리하는 역할을 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Encoding and Decoding으로 분류된 Publisher와 Operator에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Encoding and Decoding&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름에서 직관적으로 느껴지듯 Upstream Publisher에서 받은 값을 encode, decode하는 역할을 할 거 같죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Encoding and Decoding에 분류된 Publisher는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Encode&lt;/li&gt;
&lt;li&gt;Decode&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 당연한 녀석들이 있는거 같습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이들을 활용해서 만든 Operator는 아래와 같아요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;encode(encoder:)&lt;/li&gt;
&lt;li&gt;decode(type:decoder:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;encode, decode가 뭔지 간단하게 짚고 넘어가자면,&amp;nbsp;encode는 어떤 정보를 컴퓨터에서 사용하는 형태(코드)로 바꾸는 것이고,&amp;nbsp;decode는 반대로 컴퓨터에서 사용하는 형태(코드)로 바꿔진 값을 원래대로 되돌리는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 알아보도록 할게요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Encode&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbQs8P/btrDrQyJVvy/a3ACQRfkxFCnXXU5Q9lTN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbQs8P/btrDrQyJVvy/a3ACQRfkxFCnXXU5Q9lTN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbQs8P/btrDrQyJVvy/a3ACQRfkxFCnXXU5Q9lTN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbQs8P%2FbtrDrQyJVvy%2Fa3ACQRfkxFCnXXU5Q9lTN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;268&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름부터 &quot;값 받아서 인코딩할 거야&quot;라고 하는 거 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 봐도 Upstream Publisher에게 값을 받아서 주어진 encoder로 encode 하는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하니 바로 Operator로 넘어가 볼게요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;encode(encoder:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0SbDf/btrDuwNhQ9f/WjkE7xlRxnCo0B80JLWRm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0SbDf/btrDuwNhQ9f/WjkE7xlRxnCo0B80JLWRm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0SbDf/btrDuwNhQ9f/WjkE7xlRxnCo0B80JLWRm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0SbDf%2FbtrDuwNhQ9f%2FWjkE7xlRxnCo0B80JLWRm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1330&quot; height=&quot;588&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Encode Publisher를 활용해서 만든 Operator는 encode(encoder:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 upstream에서 받은 값을 특정 encoder로 encode 해서 downstream으로 전달하는 역할을 한다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 Output 타입은 Encodable 프로토콜을 준수해야 합니다.&lt;/p&gt;
&lt;p 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_1653831900947&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Penguin: Codable {
    let name: String
    let age: Int
    let address: String
}

let penguinPublisher = PassthroughSubject&amp;lt;Penguin, Never&amp;gt;()
penguinPublisher
    .encode(encoder: JSONEncoder())
    .sink(receiveCompletion: { print($0) },
          receiveValue: { data in
        print(&quot;인코딩 된 값: \(data)&quot;)
        guard let string = String(data: data, encoding: .utf8) else { return }
        print(&quot;문자열 표현: \(string)&quot;)
    })

penguinPublisher.send(.init(name: &quot;Pingu&quot;, age: 5, address: &quot;Antarctic igloo&quot;))

// encode(encoder:) 예제 코드
인코딩 된 값: 52 bytes
문자열 표현: {&quot;name&quot;:&quot;Pingu&quot;,&quot;age&quot;:5,&quot;address&quot;:&quot;Antarctic igloo&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Upstream Publisher에서 전달받은 값을 Data 타입으로 인코딩해서 Downstream으로 전달해준 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Decode&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN2JJ2/btrDpOBs2b1/CbiSWnzwFf544Z5itw1jBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN2JJ2/btrDpOBs2b1/CbiSWnzwFf544Z5itw1jBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN2JJ2/btrDpOBs2b1/CbiSWnzwFf544Z5itw1jBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN2JJ2%2FbtrDpOBs2b1%2FCbiSWnzwFf544Z5itw1jBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;298&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 것은 Decode입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream Publisher에서 값을 받으면 주어진 Decoder로 변환해서 Downstream으로 전달하는 역할을 한다고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;decode(type:decoder:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PvCaw/btrDnrUrjmD/BHe2lfF3KLHsBdBwtlLRN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PvCaw/btrDnrUrjmD/BHe2lfF3KLHsBdBwtlLRN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PvCaw/btrDnrUrjmD/BHe2lfF3KLHsBdBwtlLRN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPvCaw%2FbtrDnrUrjmD%2FBHe2lfF3KLHsBdBwtlLRN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;572&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Decode Publisher를 활용해서 만든 Operator는 decode(type:decoder:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값을 특정한 Decoder로 디코딩하는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p 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_1653832757271&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Penguin: Codable {
    let name: String
    let age: Int
    let address: String
}

let dataPublisher = PassthroughSubject&amp;lt;Data, Never&amp;gt;()
dataPublisher
    .decode(type: Penguin.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print(&quot;디코딩 된 값: \($0)&quot;) })

let jsonString = &quot;&quot;&quot;
    {&quot;name&quot;:&quot;Pingu&quot;,&quot;age&quot;:5,&quot;address&quot;:&quot;Antarctic igloo&quot;}
&quot;&quot;&quot;

dataPublisher.send(Data(jsonString.utf8))

// decode(type:decoder:) 예제 코드
디코딩 된 값: Penguin(name: &quot;Pingu&quot;, age: 5, address: &quot;Antarctic igloo&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Encode 예제와는 다르게 이번에는 json String의 data를 Penguin 구조체 형식으로 잘 디코딩한 것을 볼 수 있습니다.&lt;/p&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의 Publisher와 Operator 중에서 Encoding and Decoding으로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Encoding%20and%20Decoding.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>decode</category>
      <category>Encode</category>
      <category>IOS</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/290</guid>
      <comments>https://icksw.tistory.com/290#entry290comment</comments>
      <pubDate>Sun, 29 May 2022 23:06:10 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Controlling Timing - Operator 공부 13</title>
      <link>https://icksw.tistory.com/289</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/288&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Handling Errors로 분류된 Publisher, Operator에 대해 알아봤습니다. 이름 그대로 에러를 처리하는 역할을 했었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Controlling Timing으로 분류된 Publisher와 Operator에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controlling Timing&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류된 이름에서 느낄 수 있듯 이번에 알아볼 녀석들은 뭔가 시간에 관련된 것 들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controlling Timing에 분류된 Publisher는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MeasureInterval&lt;/li&gt;
&lt;li&gt;Debounce&lt;/li&gt;
&lt;li&gt;Delay&lt;/li&gt;
&lt;li&gt;Throttle&lt;/li&gt;
&lt;li&gt;Timeout&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이를 활용해서 만들어진 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;measureInterval(using:options:)&lt;/li&gt;
&lt;li&gt;debounce(for:scheduler:options:)&lt;/li&gt;
&lt;li&gt;delay(for:tolerance:scheduler:options:)&lt;/li&gt;
&lt;li&gt;throttle(for:scheduler:latest:)&lt;/li&gt;
&lt;li&gt;timeout(_:,scheduler:options:customError:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 자세히 알아보도록 할게요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Measurelnterval&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJVtip/btrDjPnFbgD/gEwWnqgELciCf7b1AWT2W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJVtip/btrDjPnFbgD/gEwWnqgELciCf7b1AWT2W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJVtip/btrDjPnFbgD/gEwWnqgELciCf7b1AWT2W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJVtip%2FbtrDjPnFbgD%2FgEwWnqgELciCf7b1AWT2W0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;274&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 첫 번째로 알아볼 Publisher는 MeasureInterval입니다. 정의를 보면 Upstream Publisher에서 값들이 얼마만큼의 시간 간격을 두고 전달받는지 알아낼 수 있는 Publisher라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적이라 이해가 잘 되는거 같아요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;measureInterval(using:options:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b210pt/btrDkobFiIX/Rgx1dLygAI6Z4Zvamydqk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b210pt/btrDkobFiIX/Rgx1dLygAI6Z4Zvamydqk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b210pt/btrDkobFiIX/Rgx1dLygAI6Z4Zvamydqk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb210pt%2FbtrDkobFiIX%2FRgx1dLygAI6Z4Zvamydqk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1302&quot; height=&quot;550&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MeasureInterval Publisher를 활용해서 만든 Operator는 measrueInterval(using:options:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream Publisher에서 전달받은 이벤트 사이의 시간을 측정하는 역할을 한다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 매개변수가 2개인데 알아보면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;using
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트의 시간을 추적할 스케줄러&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;options
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;using에서 사용된 스케줄러에 적용할 옵션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용할 수 있는 스케줄러에는 어떤 것들이 있는지 간단하게 알아보도록 할게요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ImmediateScheduler&lt;/li&gt;
&lt;li&gt;RunLoop&lt;/li&gt;
&lt;li&gt;DispatchQueue&lt;/li&gt;
&lt;li&gt;OperationQueue&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 네 개의 스케줄러를 사용할 수 있는데,&amp;nbsp;각 스케줄러에 대한 정리는 나중에 다른 글에서 하도록 하겠습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 일단 다시 Operator로 돌아와서 간단하게 사용해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1653715595385&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()

let intPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()

intPublisher
    .measureInterval(using: DispatchQueue.main, options: nil)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { nanoSecond in
        print(&quot;Measure Time: \(floor(Double(nanoSecond.magnitude) / 1_000_000_000.0))초&quot;)
    })
    .store(in: &amp;amp;subscriptions)

intPublisher.send(1)
sleep(1)
intPublisher.send(2)
sleep(2)
intPublisher.send(3)

// measureInterval(using:options:) 예제 코드
Measure Time: 0.0초
Measure Time: 1.0초
Measure Time: 2.0초&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 sleep 메서드를 사용해서 예제를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchQueue의 timeInterval 단위가 나노초 이기 때문에 보기 쉽게 초 단위로 바꾸기 위한 연산도 처리해줬어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 Upstream에서 받은 값 사이의 시간이 1초, 2초라고 잘 나오는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝입니다~&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Debounce&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC03AR/btrDlZCArhP/g8aT3GmZI7jSGEnpddEypk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC03AR/btrDlZCArhP/g8aT3GmZI7jSGEnpddEypk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC03AR/btrDlZCArhP/g8aT3GmZI7jSGEnpddEypk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC03AR%2FbtrDlZCArhP%2Fg8aT3GmZI7jSGEnpddEypk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;258&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 Debounce입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Publisher가 내보내는 값 사이에 일정 시간을 두고 값을 내보낸다라고 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 동작될까에 대한 건 실제로 구현된 Operator의 예제를 보면 쉽게 이해할 수 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;debounce(for:scheduler:options:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bef32t/btrDkwum6hR/wZ0QADW6Prfo2ss40FnwQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bef32t/btrDkwum6hR/wZ0QADW6Prfo2ss40FnwQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bef32t/btrDkwum6hR/wZ0QADW6Prfo2ss40FnwQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbef32t%2FbtrDkwum6hR%2FwZ0QADW6Prfo2ss40FnwQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;572&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debounce Publisher를 활용해서 만들어진 Operator는 debounce(for:scheduler:options:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 내보내는 이벤트 사이에 설정한 시간 간격이 지났을 때만 값을 Downstream으로 내려보내는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와는 다르게 dueTime이라는 매개변수가 하나 더 있네요. 이거만 알아보고 갈게용.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dueTime
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 내보내기 전에 기다리는 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작동원리는 간단합니다. 값을 받은 이후에 dueTime이 지날 때까지 값을 받지 않을 때만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 iOS 앱에서 활용하는 방법을 간단히 떠올려보면 텍스트 필드에 활용할 수 있을 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 필드의 값으로 네트워크 요청을 보내고 싶다고 할 때&amp;nbsp;텍스트 필드의 값이 바뀔 때마다 네트워크 요청을 하는 것이 아닌 입력 후에 일정 시간이 지나면 입력을 완료했다고 판단하고 그때만 요청하는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 간단하게 방금 상황을 예제로 만들어볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1653718319942&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()

let operationQueue: OperationQueue = {
    let operationQueue = OperationQueue()

    operationQueue.maxConcurrentOperationCount = 1
    return operationQueue
}()

let textField = PassthroughSubject&amp;lt;String, Never&amp;gt;()
let bounces: [(String, TimeInterval)] = [ // (입력값, 입력 후 기다리는 시간)
    (&quot;www&quot;, 0.5),
    (&quot;.&quot;, 0.5),
    (&quot;p&quot;, 1),
    (&quot;ing&quot;, 0.5),
    (&quot;u&quot;, 0.5),
    (&quot;.&quot;, 1),
    (&quot;co&quot;, 0.5),
    (&quot;m&quot;, 1),
]
var requestString: String = &quot;&quot;
textField
    .debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { string in
        print(&quot;이번에 받은 값: \(string) , Network Request with: \(requestString)&quot;)
    })
    .store(in: &amp;amp;subscriptions)

for bounce in bounces {
    operationQueue.addOperation {
        requestString += bounce.0
        textField.send(bounce.0)

        usleep(UInt32(bounce.1 * 1000000))
    }
}

// debounce(for:scheduler:options:) 예제 코드
이번에 받은 값: p , Network Request with: www.p
이번에 받은 값: . , Network Request with: www.pingu.
이번에 받은 값: m , Network Request with: www.pingu.com&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;bounces라는 배열은 (입력값, 입력 후 기다리는 시간)이라고 가정하고 봐주시면 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debounce의 dueTime에 1초라는 시간을 줬기 때문에 값을 받은 이후에 1초 이상 다음 값이 오지 않을 때만 downStream에 최근에 받은 값을 전달하는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 값을 보내고 1초 동안 기다렸던 &quot;p&quot;, &quot;.&quot;, &quot;m&quot;을 입력한 뒤에만 downstream에 전달되어 sink가 동작한 것을 볼 수 있습니다.&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;Delay&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vF5o8/btrDljnLhKT/TnWQDkPckvCRdAk6Lurpb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vF5o8/btrDljnLhKT/TnWQDkPckvCRdAk6Lurpb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vF5o8/btrDljnLhKT/TnWQDkPckvCRdAk6Lurpb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvF5o8%2FbtrDljnLhKT%2FTnWQDkPckvCRdAk6Lurpb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;266&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 Delay입니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;delay(for:tolerance:scheduler:options:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lvJ9L/btrDoboKJrj/c8rut3g9YOnOIENrNilEd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lvJ9L/btrDoboKJrj/c8rut3g9YOnOIENrNilEd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lvJ9L/btrDoboKJrj/c8rut3g9YOnOIENrNilEd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlvJ9L%2FbtrDoboKJrj%2Fc8rut3g9YOnOIENrNilEd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;608&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Delay Publisher를 활용해서 만든 Operator는 delay(for:tolerance:scheduler:options:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의는 Publisher의 값을 Downstream으로 보내기 전에 일정 시간만큼 기다리는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 매개변수를 잠깐 확인하고 가겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;interval
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Downstream으로 값을 내려보내기 전에 기다릴 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;tolerance
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 전달할 때 허용할 오차&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으로 이해할 수 있는 Operator이니 공식문서의 예제로 간단하고 보고 넘어가겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1653720355608&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()

let dateFormmater: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .none
    dateFormatter.timeStyle = .long
    return dateFormatter
}()

Timer.publish(every: 1.0, on: .main, in: .default)
    .autoconnect()
    .handleEvents(receiveOutput: { date in
        print(&quot;Downstream으로 보낸값(현재시간): \(dateFormmater.string(from: date))&quot;)
    })
    .delay(for: .seconds(3), scheduler: RunLoop.main, options: .none)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { value in
        let now = Date()
        print(&quot;받은 값: \(dateFormmater.string(from: value)) 보낸시간: \(String(format: &quot;%.2f&quot;, now.timeIntervalSince(value))) secs ago&quot;, terminator: &quot;\n&quot;)
    })
    .store(in: &amp;amp;subscriptions)
    
// delay(for:tolerance:scheduler:options:) 예제 코드
Downstream으로 보낸값(현재시간): 3:45:23 PM GMT+9
Downstream으로 보낸값(현재시간): 3:45:24 PM GMT+9
Downstream으로 보낸값(현재시간): 3:45:25 PM GMT+9
Downstream으로 보낸값(현재시간): 3:45:26 PM GMT+9
받은 값: 3:45:23 PM GMT+9 보낸시간: 3.00 secs ago
Downstream으로 보낸값(현재시간): 3:45:27 PM GMT+9
받은 값: 3:45:24 PM GMT+9 보낸시간: 3.00 secs ago
Downstream으로 보낸값(현재시간): 3:45:28 PM GMT+9
받은 값: 3:45:25 PM GMT+9 보낸시간: 3.00 secs ago&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 delay에 3초를 줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Upstream Publisher에서 내려보낸 값이 3초 뒤에야 Downstream에 전달되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 이해가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Downstream에서 받은 값은 현재 시간보다 3초 전의 값인 것을 볼 수 있어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Throttle&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI2uTJ/btrDiUbBsKj/lSRWBDSzh4d1Ej02snkC40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI2uTJ/btrDiUbBsKj/lSRWBDSzh4d1Ej02snkC40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI2uTJ/btrDiUbBsKj/lSRWBDSzh4d1Ej02snkC40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI2uTJ%2FbtrDiUbBsKj%2FlSRWBDSzh4d1Ej02snkC40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;272&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 Throttle입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 지정된 시간 간격마다 Upstream Publisher가 보낸 가장 최근 값 혹은 가장 첫 번째 값을 Downstream으로 전달하는 Publisher라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 알아본 Debounce와 조금 비슷한 부분도 있는데요, 차이점은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Debounce
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값의 수신이 멈추면 일정 시간을 기다린 후 가장 최신 값을 Downstream으로 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Throttle
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 시간을 기다린 뒤 해당 시간 동안 수신한 값 중 가장 첫 번째 값이나 최신 값을 Downstream으로 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Operator를 사용해보면 이해가 바로 되실 거예요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;throttle(for:scheduler:latest:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xi1Dr/btrDjN4teKf/BmEPyGAMGooR57INjIaL1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xi1Dr/btrDjN4teKf/BmEPyGAMGooR57INjIaL1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xi1Dr/btrDjN4teKf/BmEPyGAMGooR57INjIaL1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxi1Dr%2FbtrDjN4teKf%2FBmEPyGAMGooR57INjIaL1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1310&quot; height=&quot;572&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Throttle Publisher를 활용해서 만들어진 Operator는 throttle(for:scheduler:latest:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 일정 시간 간격 동안 Upstream Publisher에게 받은 값 중 최신 값 혹은 첫 번째 값을 Downstream으로 전달하는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수도 정의를 이해했다면 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;interval
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 내려보내기 전에 Upstream Publisher에게 값을 받는 시간 간격입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;latest
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 시간 동안 받은 값 중 최신 값을 내려보낼지 첫 번째 값을 내려보낼지 결정하는 Bool 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용해보면 바로 이해가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1653721653577&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()

let operationQueue: OperationQueue = {
    let operationQueue = OperationQueue()

    operationQueue.maxConcurrentOperationCount = 1
    return operationQueue
}()

let textField = PassthroughSubject&amp;lt;String, Never&amp;gt;()
let throttles: [(String, TimeInterval)] = [ // (입력값, 입력에 걸리는 시간)
    (&quot;www&quot;, 0.5),
    (&quot;.&quot;, 0.5),
    (&quot;p&quot;, 1),
    (&quot;ing&quot;, 0.5),
    (&quot;u&quot;, 0.5),
    (&quot;.&quot;, 5),
    (&quot;co&quot;, 0.5),
    (&quot;m&quot;, 2),
]
var requestString: String = &quot;&quot;
textField
    .throttle(for: .seconds(2), scheduler: DispatchQueue.main, latest: true)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { string in
        print(&quot;===Throttle===\n이번시간동안 받은 값중 최신값: \(string), 현재시간: \(Date().description), Network Request with: \(requestString)\n&quot;)
    })
    .store(in: &amp;amp;subscriptions)

textField
    .sink(receiveCompletion: { print($0) },
          receiveValue: { string in
        print(&quot;===Subject===\n현재시간: \(Date().description), 이번에 내려보낸 값: \(string)&quot;)
    })
    .store(in: &amp;amp;subscriptions)

for throttle in throttles {
    operationQueue.addOperation {
        requestString += throttle.0
        textField.send(throttle.0)

        usleep(UInt32(throttle.1 * 1000000))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debounce와의 차이를 알아보기 위해 Debounce 예제 때와 유사하게 텍스트 필드에 값을 입력하는 상황을 예제로 만들어봤습니다.&lt;/p&gt;
&lt;p 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_1653721687949&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// throttle(for:scheduler:latest:) 예제 코드
===Subject===
현재시간: 2022-05-28 07:12:02 +0000, 이번에 내려보낸 값: www
===Throttle===
이번시간동안 받은 값중 최신값: www, 현재시간: 2022-05-28 07:12:02 +0000, Network Request with: www

===Subject===
현재시간: 2022-05-28 07:12:02 +0000, 이번에 내려보낸 값: .
===Subject===
현재시간: 2022-05-28 07:12:03 +0000, 이번에 내려보낸 값: p
===Subject===
현재시간: 2022-05-28 07:12:04 +0000, 이번에 내려보낸 값: ing
===Throttle===
이번시간동안 받은 값중 최신값: ing, 현재시간: 2022-05-28 07:12:04 +0000, Network Request with: www.ping

===Subject===
현재시간: 2022-05-28 07:12:04 +0000, 이번에 내려보낸 값: u
===Subject===
현재시간: 2022-05-28 07:12:05 +0000, 이번에 내려보낸 값: .
===Throttle===
이번시간동안 받은 값중 최신값: ., 현재시간: 2022-05-28 07:12:06 +0000, Network Request with: www.pingu.

===Subject===
현재시간: 2022-05-28 07:12:10 +0000, 이번에 내려보낸 값: co
===Throttle===
이번시간동안 받은 값중 최신값: co, 현재시간: 2022-05-28 07:12:10 +0000, Network Request with: www.pingu.co

===Subject===
현재시간: 2022-05-28 07:12:10 +0000, 이번에 내려보낸 값: m
===Throttle===
이번시간동안 받은 값중 최신값: m, 현재시간: 2022-05-28 07:12:12 +0000, Network Request with: www.pingu.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 첫 번째 값을 내려보낸 이후부터는 2초 동안 받은 값 중 가장 최신 값만 downstream으로 전달하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debounce와의 차이가 보이시나용?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debounce는 Downstream에 값을 내려보낸 뒤 일정 시간을 기다린 뒤 최신 값을 내려보냈다면, Throttle은 그냥 일정 시간이 지났을 때 Upstream에서 받은 값이 있다면 최신 값 혹은 첫 번째 값을 내려보내게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;throttle이 작동되는 시간 동안 Upstream에서 받은 값이 없는 경우가 있을 수 있습니다. &lt;span&gt;위 예제에서 값을 내려보낸 뒤에 5초 동안 아무것도 안 보내는 구간이 있는데요,&lt;span&gt;&amp;nbsp;그땐 throttle이 작동되지 않고 그냥 다음 주기로 넘어가게 되는 것을 볼 수 있어요.&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;정리하자면, throttle을 사용하면 일정 시간 동안 Upstream Publisher에게 받은 값 중 최신 값 또는 첫 번째 값을 Downstream에 전달할 수 있다! 입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Timeout&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPVVZi/btrDiSEREDr/p7V6d2RYKS8kaVwzl0owIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPVVZi/btrDiSEREDr/p7V6d2RYKS8kaVwzl0owIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPVVZi/btrDiSEREDr/p7V6d2RYKS8kaVwzl0owIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPVVZi%2FbtrDiSEREDr%2Fp7V6d2RYKS8kaVwzl0owIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;950&quot; height=&quot;256&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 마지막으로 알아볼 Publisher는 Timeout입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 정해진 시간 동안 Upstream에서 값을 받지 못하면 finish 해버리는 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 거 같으니 바로 Operator로 넘어가 볼게요!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;timeout(_:scheduler:options:customError:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKakJj/btrDobCicRV/anM0rGHZlKcGPh0Kn8Kndk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKakJj/btrDobCicRV/anM0rGHZlKcGPh0Kn8Kndk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKakJj/btrDobCicRV/anM0rGHZlKcGPh0Kn8Kndk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKakJj%2FbtrDobCicRV%2FanM0rGHZlKcGPh0Kn8Kndk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1318&quot; height=&quot;598&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Timeout Publisher를 활용해서 만든 Operator는 timeout(_:scheduler:options:customError:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 정해진 시간 동안 Upstream Publisher에게 값을 받지 못하면 Publisher를 완료하거나 에러를 발생시키는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수를 간단하게 살펴볼게요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;interval
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 전달받지 않아도 되는 최대 시간입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;customError
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 시간동안 값을 받지 못했을 때 실행될 클로저입니다. Failure 타입을 내려보낼 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱히 어려운 건 없으니 간단하게 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1653723740242&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()

struct TimeOutError: Error { }

let intPublisher = PassthroughSubject&amp;lt;Int, TimeOutError&amp;gt;()

intPublisher
    .timeout(.seconds(2), scheduler: DispatchQueue.main, customError: {
        return TimeOutError()
    })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
    .store(in: &amp;amp;subscriptions)

intPublisher.send(1)
intPublisher.send(2)

DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
    intPublisher.send(3)
}

// timeout(_:scheduler:options:customError:) 예제 코드
1
2
failure(__lldb_expr_185.(unknown context at $10f0c663c).(unknown context at $10f0c6644).(unknown context at $10f0c664c).TimeOutError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 2초 안에 값을 전달받지 못하면 에러를 발생시키게 만들고, 2.5초 뒤에 값을 내보내도록 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 1, 2는 Downstream에 전달됐지만 2.5초 뒤에 전달되는 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;그리고 만약 customError 클로저에서 에러를 반환하지 않으면 그냥 finished 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&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;이렇게 Combine의 Publisher와 Operator 중에서 Controlling Timing으로 분류된 것들에 대해 알아봤습니다. 시간에 관련되다 보니 아주 유용할 거 같네요.&lt;/p&gt;
&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://icksw.tistory.com/290&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Encoding and Decoding으로 분류된 것들에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Controlling%20Timing.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>debounce</category>
      <category>Delay</category>
      <category>IOS</category>
      <category>MeasureInterval</category>
      <category>Swift</category>
      <category>Throttle</category>
      <category>timeout</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/289</guid>
      <comments>https://icksw.tistory.com/289#entry289comment</comments>
      <pubDate>Sat, 28 May 2022 16:47:40 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Handling Errors - Operator 공부 12</title>
      <link>https://icksw.tistory.com/288</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;안녕하세요 Pingu입니다. &lt;br&gt; &lt;br&gt;&lt;a href=&quot;https://icksw.tistory.com/287&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;지난 글&lt;/span&gt;&lt;/a&gt;에서는 Combine의 Republishing Elements by Subscribing to New Publishers로 분류된 FlatMap, SwitchToLatest에 대해 알아봤습니다. 여러 개의 Publisher들 중 몇 개를 subscribe 할지 혹은 가장 최근에 subscribe 한 Publisher의 값만 Downstream으로 전달하는 역할을 했었습니다.&lt;br&gt; &lt;br&gt;이번 글에서는 Handling Errors로 분류된 Publisher와 Operator에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;Handling Errors&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;분류된 이름에서 느낄 수 있듯 에러를 처리하는 Publisher와 Operator들을 알아보겠습니다.&lt;br&gt;Handling Errors에 분류된 Publisher들은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;AssertNoFailure&lt;/li&gt;
 &lt;li&gt;Catch&lt;/li&gt;
 &lt;li&gt;TryCatch&lt;/li&gt;
 &lt;li&gt;Retry&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;그리고 이를 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;assertNoFailure(_:file:line:)&lt;/li&gt;
 &lt;li&gt;catch(_:)&lt;/li&gt;
 &lt;li&gt;tryCatch(_:)&lt;/li&gt;
 &lt;li&gt;retry(_:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;그럼 바로 하나씩 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;AssertNoFailure&lt;/h2&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HJgUe/btrC1g0fsYW/yk2zm9IaDV618L72BlEDx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HJgUe/btrC1g0fsYW/yk2zm9IaDV618L72BlEDx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HJgUe/btrC1g0fsYW/yk2zm9IaDV618L72BlEDx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHJgUe%2FbtrC1g0fsYW%2Fyk2zm9IaDV618L72BlEDx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;238&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;가장 먼저 알아볼 Publisher는 AssertNoFailure 입니다. 정의를 보면 failure event를 받으면 fatal error를 downstream에 전달하는 Publisher라고 되어있네요. 그 외의 경우엔 모두 Downstream으로 전달한다고 합니다.&lt;br&gt; &lt;br&gt;즉 Upstream이 절대로 실패하면 안되는 타입이어야만 문제없이 동작하도록 하는 Publisher입니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;assertNoFailure(_:file:line:)&lt;/h3&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU1bfx/btrC4hwj2th/Vh9VSqWdNVoXnxx7XxefTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU1bfx/btrC4hwj2th/Vh9VSqWdNVoXnxx7XxefTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU1bfx/btrC4hwj2th/Vh9VSqWdNVoXnxx7XxefTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU1bfx%2FbtrC4hwj2th%2FVh9VSqWdNVoXnxx7XxefTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;815&quot; height=&quot;346&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;AssertNoFailure Publisher를 활용해서 만든 Operator는 assertNoFailure(_:file:line:) 입니다.&lt;br&gt;정의를 보면 Upstream Publisher에서 failure를 받으면 fatal error를 발생시키는 역할을 한다고 합니다.&lt;br&gt;그리고 Failure를 제외한 값은 모두 Downstream으로 전달합니다.&lt;br&gt; &lt;br&gt;간단하게 한 번 사용해볼게요.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPublisher = PassthroughSubject&amp;lt;Int, PinguError&amp;gt;()

intPublisher
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.assertNoFailure()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveCompletion: { print($0) },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;receiveValue: { print($0) })

intPublisher.send(1)
intPublisher.send(2)
intPublisher.send(completion: .failure(PinguError())) // fatal Error 발생

// assertNoFailure(_:file:line:) 예제 코드
1
2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;위와 같이 간단히 사용해볼 수 있습니다. 위 코드를 실제로 실행해보면 에러를 내보내기 전까지는 정상적으로 값이 Downstream에 전달되고 있지만 failure를 내려보내면 fatal error가 발생하는 것을 볼 수 있을 거예요.&lt;br&gt; &lt;br&gt;이러한 특징 때문에 실제 서비스에서 사용하는 것은 위험할 수 있지만, 개발할 때나 테스트할 때는 유용하게 사용할 수 있을 거 같네요.&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;Catch&lt;/h2&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be12Uu/btrC1SEOsEY/0FcTMdY9dVl6bTHWRSRg4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be12Uu/btrC1SEOsEY/0FcTMdY9dVl6bTHWRSRg4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be12Uu/btrC1SEOsEY/0FcTMdY9dVl6bTHWRSRg4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe12Uu%2FbtrC1SEOsEY%2F0FcTMdY9dVl6bTHWRSRg4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;262&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;다음으로 알아볼 것은 Catch입니다.&lt;br&gt;정의를 보면 실패한 Publisher를 다른 Publisher로 바꿔서 Upstream에서 에러가 발생하더라고 처리할 수 있는 Publisher라고 하네요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;catch(_:)&lt;/h3&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXfaZV/btrC1SY7rUE/KQ26XnRcMMAT4nIjT6tLkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXfaZV/btrC1SY7rUE/KQ26XnRcMMAT4nIjT6tLkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXfaZV/btrC1SY7rUE/KQ26XnRcMMAT4nIjT6tLkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXfaZV%2FbtrC1SY7rUE%2FKQ26XnRcMMAT4nIjT6tLkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;324&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Catch Publisher를 활용해서 만든 Operator는 catch(_:)입니다.&lt;br&gt;정의를 보면 Upstream의 Publisher에서 에러가 발생하면 다른 Publisher로 교체해서 처리하는 역할을 한다고 합니다.&lt;br&gt; &lt;br&gt;간단하게 한 번 사용해볼게요.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPublisher = [4, 6, 5, 12, 7, 9, 10].publisher

intPublisher
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryMap { value in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard value % 2 == 0 else { throw PinguError() }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value * 2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.catch { error in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Just(-1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveCompletion: { print($0) },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;receiveValue: { print($0) })
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
// catch(_:) 예제 코드
8
12
-1
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;위 코드는 보면 짝수는 두 배로 내려보내고 홀수는 PinguError를 던지는 코드입니다.&lt;br&gt;그래서 4, 6까지는 두배로 출력되지만 5에서 PinguError를 던지게 되어 catch로 처리되는데, catch에서는 에러가 발생하면 Just(-1)로 Publisher를 바꿔버립니다.&lt;br&gt;그래서 -1이 내보내지고 끝나게 됩니다.&lt;br&gt; &lt;br&gt;간단하죠?&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;TryCatch&lt;/h2&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mGCPj/btrC1iwUa8t/qzefZvKyhoDNBe8KnG1W61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mGCPj/btrC1iwUa8t/qzefZvKyhoDNBe8KnG1W61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mGCPj/btrC1iwUa8t/qzefZvKyhoDNBe8KnG1W61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmGCPj%2FbtrC1iwUa8t%2FqzefZvKyhoDNBe8KnG1W61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;296&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;이번엔 TryCatch를 알아보겠습니다.&lt;br&gt;보통 Try는 기존 거에 에러를 던질 수 있다는 차이밖에 없었는데요, 이번에도 동일합니다.&lt;br&gt;아까 Catch는 에러를 받으면 다른 Publisher로 바꾸기만 했다면 TryCatch는 다른 Publisher로 바꾸거나 다른 에러를 던질 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;tryCatch(_:)&lt;/h3&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX0m0S/btrC3dgV268/oMmllNIS2Vs9WoKbHV9TM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX0m0S/btrC3dgV268/oMmllNIS2Vs9WoKbHV9TM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX0m0S/btrC3dgV268/oMmllNIS2Vs9WoKbHV9TM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX0m0S%2FbtrC3dgV268%2FoMmllNIS2Vs9WoKbHV9TM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;353&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;TryCatch Publisher를 활용해서 만든 Operator는 tryCatch(_:)이고 catch(_:)와는 에러를 던질 수 있다는 차이점만 존재하니 바로 사용해보고 넘어가겠습니다.&lt;br&gt; &lt;br&gt;이번에는 에러가 발생하면 다른 Publisher로 바꾸는 예제를 만들어볼게요.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPublisher = [4, 6, 7].publisher
let anotherPublisher = [10, 11].publisher

intPublisher
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryMap { value in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard value % 2 == 0 else { throw PinguError() }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value * 2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryCatch({ error -&amp;gt; AnyPublisher&amp;lt;Int, Never&amp;gt; in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return anotherPublisher.eraseToAnyPublisher()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveCompletion: { print($0) },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;receiveValue: { print($0) })
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
// tryCatch(_:) 예제 코드
8
12
10
11
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;이렇게 작성하면 tryMap에서 에러가 발생했을 때 tryCatch에서 다른 Publisher로 교체하는 결과를 볼 수 있습니다.&lt;br&gt; &lt;br&gt;그럼 이번에는 tryCatch에 에러가 전달되면 다른 에러를 던지도록 해볼게요.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PinguError: Error { }
struct AnotherError: Error { }
let intPublisher = [4, 6, 7].publisher
let anotherPublisher = [10, 11].publisher
intPublisher
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryMap { value in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard value % 2 == 0 else { throw PinguError() }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value * 2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryCatch({ error -&amp;gt; AnyPublisher&amp;lt;Int, Never&amp;gt; in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if error is PinguError { throw AnotherError() }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return anotherPublisher.eraseToAnyPublisher()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveCompletion: { print($0) },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;receiveValue: { print($0) })
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
// tryCatch(_:) 예제 코드
8
12
failure(__lldb_expr_56.(unknown context at $10855f15c).(unknown context at $10855f1f4).(unknown context at $10855f220).AnotherError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;위 코드는 tryCatch에 전달된 에러가 PinguError라면 AnotherError로 바꿔서 던지는 코드입니다.&lt;br&gt;크게 어려운 점은 없는 듯합니다!&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;Retry&lt;/h2&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjF0fc/btrC1iX3jN0/d1dLrxACOGcXcCGPc0Vod1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjF0fc/btrC1iX3jN0/d1dLrxACOGcXcCGPc0Vod1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjF0fc/btrC1iX3jN0/d1dLrxACOGcXcCGPc0Vod1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjF0fc%2FbtrC1iX3jN0%2Fd1dLrxACOGcXcCGPc0Vod1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;974&quot; height=&quot;214&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;마지막으로 알아볼 Publisher는 Retry입니다.&lt;br&gt;정의를 보면 Upstream Publisher가 실패한다고 해도 새로운 subscription을 만들어서 다시 시도하는 Publisher라고 하네요.&lt;br&gt;네트워크 에러와 같은 상황이 발생할 때 몇 번 더 시도하고 에러를 발생시키도록 하는 등의 다양한 활용 방법이 있을 거 같은 Publisher인 거 같습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;retry(_:)&lt;/h3&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XhUqp/btrC2Lrtnwr/HHHr6Mw7g6FkT2dHhAPhc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XhUqp/btrC2Lrtnwr/HHHr6Mw7g6FkT2dHhAPhc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XhUqp/btrC2Lrtnwr/HHHr6Mw7g6FkT2dHhAPhc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXhUqp%2FbtrC2Lrtnwr%2FHHHr6Mw7g6FkT2dHhAPhc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;313&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Retry Publisher를 활용해서 만들어진 Operator는 retry(_:)입니다.&lt;br&gt;정의를 보면 upstream Publisher가 실패하더라도 매개변수로 주어진 횟수만큼 재시도하는 역할을 한다고 합니다.&lt;br&gt;아주 직관적이네요.&lt;br&gt; &lt;br&gt;바로 사용해볼게요.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PinguError: Error { }

var retryCount: Int = 0

func retryTest() throws {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if retryCount &amp;lt; 2 {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retryCount += 1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;\(retryCount) 번째 재시도&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw PinguError()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

let intPublisher = [1, 2, 3, 4].publisher

intPublisher
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryMap({ value -&amp;gt; Int in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try retryTest()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retry(3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveCompletion: { print($0) },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;receiveValue: { print(&quot;receive: \($0)&quot;) })
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
// retry(_:) 예제 코드
1 번째 재시도
2 번째 재시도
receive: 1
receive: 2
receive: 3
receive: 4
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;위 코드는 3번까지는 실패해도 다시 시도하도록 retry(_:)을 사용하여 Publisher를 subscribe 한 코드입니다.&lt;br&gt;retryCount라는 변수를 만들어서 재시도할 때마다 1씩 증가시키는 것도 볼 수 있어요.&lt;br&gt;그래서 결국 3번째 재시도는 성공해서 값을 받아오게 됩니다.&lt;br&gt; &lt;br&gt;그럼 만약에 시도하는 횟수보다 실패하는 횟수가 많으면 어떻게 될까요?&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PinguError: Error { }

var retryCount: Int = 0

func retryTest() throws {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if retryCount &amp;lt; 4 {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retryCount += 1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;\(retryCount) 번째 재시도&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw PinguError()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

let intPublisher = [1, 2, 3, 4].publisher

intPublisher
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.tryMap({ value -&amp;gt; Int in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try retryTest()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retry(3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveCompletion: { print($0) },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;receiveValue: { print(&quot;receive: \($0)&quot;) })
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
// retry(_:) 예제 코드
1 번째 재시도
2 번째 재시도
3 번째 재시도
4 번째 재시도
failure(__lldb_expr_80.(unknown context at $107dd019c).(unknown context at $107dd0294).(unknown context at $107dd029c).PinguError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;위와 같이 시도하는 횟수보다 실패하는 횟수가 많다면 에러가 그대로 전달되어 failure로 끝나게 됩니다.&lt;br&gt; &lt;br&gt;이렇게 Combine의 Publisher와 Operator 중에서 Handling Errors로 분류된 것들에 대해 알아봤습니다. 실제로 앱을 만들 때 아주 유용할 거 같다는 생각이 많이 드네요.&lt;br&gt; &lt;br&gt;&lt;a href=&quot;https://icksw.tistory.com/289&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;다음 글&lt;/span&gt;&lt;/a&gt;에서는 시간과 관련된 Controlling Timing으로 분류된 것들에 대해 알아보도록 하겠습니다.&lt;br&gt; &lt;br&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Handling%20Errors.playground/Contents.swift&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;에서 볼 수 있습니다.&lt;br&gt; &lt;br&gt;감사합니다.&lt;/p&gt;</description>
      <category>iOS/Combine</category>
      <category>AssertNoFailure</category>
      <category>Catch</category>
      <category>Combine</category>
      <category>IOS</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>retry</category>
      <category>Swift</category>
      <category>trycatch</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/288</guid>
      <comments>https://icksw.tistory.com/288#entry288comment</comments>
      <pubDate>Wed, 25 May 2022 00:07:19 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Republishing Elements by Subscribing to New Publishers - Operator 공부 11</title>
      <link>https://icksw.tistory.com/287</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/286&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Combining Elements from Multiple Publishers로 분류된 것들 중 Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers 역할을 하는 Zip 시리즈에 대해 알아봤습니다. 여러 개의 Publisher에서 가장 오래 사용되지 않은 값들을 모아서 처리하는 역할을 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Republishing Elements by Subscribing to New Publishers로 분류된 것들에 대해 알아보도록 하겠습니다.&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;Republishing Elements by Subscribing to New Publishers&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번글에서 공부할 것들을 분류한 이름을 보면 새로운 Publisher를 subscribe 해서 값을 다시 내보내는 역할을 한다고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 어떤 Publisher들이 있는지 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FlatMap&lt;/li&gt;
&lt;li&gt;SwitchToLatest&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 2개의 Publisher로 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;flatMap(maxPublishers:,_:)&lt;/li&gt;
&lt;li&gt;switchToLatest()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 바로 하나씩 자세히 알아보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;FlatMap&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drVIm9/btrCuU4qeJJ/K4KHgg74M612oCIgfwFw61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drVIm9/btrCuU4qeJJ/K4KHgg74M612oCIgfwFw61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drVIm9/btrCuU4qeJJ/K4KHgg74M612oCIgfwFw61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrVIm9%2FbtrCuU4qeJJ%2FK4KHgg74M612oCIgfwFw61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;268&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 알아볼 첫 번째 Publisher는 FlatMap 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream Publisher의 값을 새로운 Publisher로 변환하는 역할을 하는 Publisher라고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이를 활용해서 만든 Operator를 보고 역할을 이해해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;flatMap(maxPublishers:,_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bubsKr/btrCyemAiIx/e4ryTwbCQKnKJ7ugHKUl9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bubsKr/btrCyemAiIx/e4ryTwbCQKnKJ7ugHKUl9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bubsKr/btrCyemAiIx/e4ryTwbCQKnKJ7ugHKUl9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbubsKr%2FbtrCyemAiIx%2Fe4ryTwbCQKnKJ7ugHKUl9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;612&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlatMap Publisher를 활용해서 만든 Operator는 flatMap(maxPublishers:_:)입니다. 정의를 보면 Upstream의 값을 지정한 최대 횟수까지 내보내는 새로운 Publisher를 반환하는 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수로는 maxPublishers가 있고 transform이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;maxPublishers의 타입은 Subscribers.Demand인걸 볼 수 있습니다. 이는 처리할 Publisher의 수라고 볼 수 있어요. 즉 몇 개의 Publisher를 처리할 것인지 정하는 것인데요, 따로 설정하지 않으면 무한 개의 Publisher를 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transform은 Upstream의 값을 사용해서 해당 타입을 내보내는 Publisher를 반환하는 클로저입니다.&lt;/p&gt;
&lt;p 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_1653324248237&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let publisher1 = PassthroughSubject&amp;lt;String, Never&amp;gt;()
let publisher2 = PassthroughSubject&amp;lt;String, Never&amp;gt;()

let publishers = PassthroughSubject&amp;lt;PassthroughSubject&amp;lt;String, Never&amp;gt;, Never&amp;gt;()

publishers
    .flatMap(maxPublishers: .max(1)) { $0 }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

publishers.send(publisher1)
publishers.send(publisher2) // 무시됨

publisher1.send(&quot;Hello&quot;)
publisher1.send(&quot;Pingu&quot;)

publisher2.send(&quot;Good Bye&quot;) // 무시됨
publisher2.send(&quot;Pinga&quot;) // 무시됨

publisher1.send(completion: .finished)
publishers.send(completion: .finished)

// flatMap(maxPublishers:_:) 예제 코드
Hello
Pingu
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 flatMap의 maxPublishers 값에 max(1)이 들어간 것을 볼 수 있는데요, 즉 한 개의 Publisher만 처리하겠다는 의미가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 위와 같이 publisher1만 처리되고 publisher2는 무시되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 flatMap을 사용하면 여러 개의 Publisher를 하나의 Publisher처럼 사용할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이번엔 flatMap을 좀 더 실용적으로 사용해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652887155450&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func decodeOnlyAlphabet(_ codes: [Int]) -&amp;gt; AnyPublisher&amp;lt;String, Never&amp;gt; {
    Just(
        codes
            .compactMap { code in
                guard (65...90).contains(code) || (97...122).contains(code) else { return nil }
                return String(UnicodeScalar(code) ?? &quot; &quot;)
            }
            .joined()
    )
    .eraseToAnyPublisher()
}

let intArrayPublisher = PassthroughSubject&amp;lt;[Int], Never&amp;gt;()
intArrayPublisher
    .flatMap(decodeOnlyAlphabet)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

intArrayPublisher.send([1, 80, 105, 110, 103, 117])
intArrayPublisher.send([1, 80, 105, 110, 103, 97])
intArrayPublisher.send(completion: .finished)

// flatMap(maxPublishers:_:) 예제 코드
Pingu
Pinga
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;위 코드를 보면 decodeOnlyAlphabet(_:)이라는 함수가 있습니다. 해당 함수는 정수 배열을 받아서 각각의 값 중 아스키코드의 알파벳으로 바꿀 수 있는 값만 String으로 바꿔서 합친 뒤 Downstream으로 내려보내는 Publisher를 반환합니다. &amp;nbsp;그리고 메서드는 flatMap의 transform 매개변수에 사용됩니다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collect()로 Upstream에서 받은 값을 모두 모아서 flatMap에 전달해주면 decodeOnlyAlphabet의 매개변수로 전달하면 flatMap은 Downstream으로 값을 전달하는 게 아닌 Publisher가 전달됩니다.&lt;/p&gt;
&lt;p 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;이렇게 flatMap을 사용하면 Upstream에서 받은 Publisher와는 다른 타입의 Publisher를 Downstream으로 전달할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SwitchToLatest&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/piIC5/btrCydVvMdU/2g3fdobtuHrKTJ0m13f1Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/piIC5/btrCydVvMdU/2g3fdobtuHrKTJ0m13f1Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/piIC5/btrCydVvMdU/2g3fdobtuHrKTJ0m13f1Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpiIC5%2FbtrCydVvMdU%2F2g3fdobtuHrKTJ0m13f1Tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;270&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 SwitchToLatest입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 중첩된 Publisher를 합치는 Publisher라고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세히 알아보기 위해 SwitchToLatest Publisher의 실제 구현을 보면 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1653318231710&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct SwitchToLatest&amp;lt;P, Upstream&amp;gt; : Publisher where P : Publisher, P == Upstream.Output, Upstream : Publisher, P.Failure == Upstream.Failure {
	public typealias Output = P.Output
	public typealias Failure = P.Failure
	public let upstream: Upstream
    
	public init(upstream: Upstream)
    
	public func receive&amp;lt;S&amp;gt;(subscriber: S) where S : Subscriber, P.Output == S.Input, Upstream.Failure == S.Failure
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자를 보면 Upstream, 즉 Publisher가 필요하다는 것을 알 수 있습니다. 즉 switchToLatest는 Publisher를 내보내는 Publisher에서만 사용할 수 있고 Upstream에서 Publisher를 전달받는다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher들을 어떻게 합치는 녀석인지는 실제로 이를 활용해서 구현된 Operator를 보면 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;switchToLatest()&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xI7F0/btrCXmkSPUG/k9KuY946pEiFIYSkoP6tgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xI7F0/btrCXmkSPUG/k9KuY946pEiFIYSkoP6tgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xI7F0/btrCXmkSPUG/k9KuY946pEiFIYSkoP6tgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxI7F0%2FbtrCXmkSPUG%2Fk9KuY946pEiFIYSkoP6tgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1300&quot; height=&quot;538&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwitchToLatest Publisher를 활용해서 만들어진 Operator는 switchToLatest()입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 가장 최근에 받은 Publisher의 값을 다시 내보낸다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 보면 저는 잘 이해가 안 됐었는데요, 간단히 말해서 SwitchToLatest는 Upstream에서 Publisher를 받으며 가장 최근에 받은 Publisher가 내보내는 값만 Downstream으로 전달합니다.&lt;/p&gt;
&lt;p 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_1653318187242&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let publisher1 = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let publisher2 = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let publisher3 = PassthroughSubject&amp;lt;Int, Never&amp;gt;()

let publishers = PassthroughSubject&amp;lt;PassthroughSubject&amp;lt;Int, Never&amp;gt;, Never&amp;gt;()

publishers
    .switchToLatest()
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

publishers.send(publisher1)
publisher1.send(1)

publishers.send(publisher2)
publisher1.send(2)  // downstream으로 전달이 안된다
publisher2.send(11)

publishers.send(publisher3)
publisher1.send(3)  // downstream으로 전달이 안된다
publisher2.send(12) // downstream으로 전달이 안된다
publisher3.send(111)

publisher3.send(completion: .finished)
publishers.send(completion: .finished)

// switchToLatest() 예제 코드
1
11
111
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 Int 값을 내보내는 publisher1, 2, 3가 있고, Publisher를 내보내는 publishers가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 publishers는 가장 최근에 내보낸 publisher의 값만 Downstream으로 전달되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwitchToLatest는 네트워크 요청 작업에서 유용하게 사용할 수 있는데요, 어떤 버튼을 누르면 네트워크 요청을 한다고 가정해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 버튼을 여러 번 탭 하는 상황이 발생하면 가장 마지막 탭에서 발생한 네트워크 요청만 처리하고 싶을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 switchToLatest를 사용하면 유용합니다.&lt;/p&gt;
&lt;p 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_1653319854618&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()

let url = URL(string: &quot;https://source.unsplash.com/random&quot;)!
    
func getImage() -&amp;gt; AnyPublisher&amp;lt;UIImage?, Never&amp;gt; {
    URLSession.shared
        .dataTaskPublisher(for: url)
        .map { data, _ in UIImage(data: data) }
        .replaceError(with: nil)
        .eraseToAnyPublisher()
}

let userTap = PassthroughSubject&amp;lt;Void, Never&amp;gt;()

userTap
    .map { _ in getImage() }
    .switchToLatest()
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
    .store(in: &amp;amp;subscriptions)

userTap.send()

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    userTap.send()
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2.01) {
    userTap.send()
}

// switchToLatest with network request 예제 코드
Optional(&amp;lt;UIImage:0x6000027e8d80 anonymous {1080, 1919} renderingMode=automatic&amp;gt;)
Optional(&amp;lt;UIImage:0x6000027e8cf0 anonymous {1080, 720} renderingMode=automatic&amp;gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 userTap이 사용자가 버튼을 탭하는 행위라고 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userTap이 3번 send() 했는데 실제로 Downstream에서 받은 이미지의 수는 2개인 것을 볼 수 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 두 번째 탭과 세 번째 탭 사이의 시간이 0.01초라서 두 번째 탭의 네트워크 요청이 완료되기 전에 세번째 탭의 네트워크 요청이 발생하여 세번째 탭의 네트워크 요청만 처리된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 첫 번째 탭의 네트워크 결과가 2초 안에 처리되지 않았다면 Downstream이 받은 이미지는 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;이렇게 가장 최근에 받은 Publisher가 내보낸 값만 Downstream으로 전달하는 역할을 하는 Operator가 switchToLatest()입니다.&lt;/p&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의 Publisher와 Operator 중에서&amp;nbsp;Republishing Elements by Subscribing to New Publishers로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&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://icksw.tistory.com/288&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Handling Error로 분류된 녀석들을 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Republishing%20Elements%20by%20Subscribing%20to%20New%20Publishers.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>flatmap</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>publishers</category>
      <category>switchToLatest</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/287</guid>
      <comments>https://icksw.tistory.com/287#entry287comment</comments>
      <pubDate>Tue, 24 May 2022 01:59:47 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Combining Elements from Multiple Publishers 3 - Operator 공부 10</title>
      <link>https://icksw.tistory.com/286</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/285&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Combining Elements from Multiple Publishers로 분류된 것들 중 Republishing Elements from Multiple Publishers as an Interleaved Stream 역할을 하는 Merge 시리즈에 대해 알아봤습니다. 여러 개의 Publisher를 하나의 Publisher처럼 사용할 수 있게 해 줬죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 드디어 Combining Elements from Multiple Publishers 시리즈의 마지막인 Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers 역할을 하는 Zip 시리즈에 대해 알아보도록 하겠습니다.&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;Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공부할 Operator들을 분류한 이름을 보면 여러 개의 Publisher에서 가장 오래 사용되지 않은 값들을 모아서 처리하는 역할을 한다고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 어떤 Publisher들이 있는지부터 보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Zip&lt;/li&gt;
&lt;li&gt;Zip3&lt;/li&gt;
&lt;li&gt;Zip4&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음 여기서도 뒤에 숫자가 늘어나는 거로 봐선 동일한 동작을 하지만 처리하는 Publisher의 개수만 달라지나 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Zip Publisher들을 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;zip(_ other:)&lt;/li&gt;
&lt;li&gt;zip(_ other: _ transform:)&lt;/li&gt;
&lt;li&gt;zip(_ publisher1, _ publisher2)&lt;/li&gt;
&lt;li&gt;zip(_ publisher1, _ publisher2, _ transform:)&lt;/li&gt;
&lt;li&gt;zip(_ publisher1, _ publisher2, _ publisher3)&lt;/li&gt;
&lt;li&gt;zip(_ publisher1, _ publisher2, _ publisher3, _ transform:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opertaor도 보면 매개변수로 Publisher만 있는 것, transform이라는 클로저도 필요한 것으로 크게 두 개로 나뉩니다. 각각은 처리하는 Publisher의 개수만 달라지는 것이므로 이번 글에서는 2개의 Publisher를 처리하는 Zip Publisher와 이를 활용해서 만든 Operator에 대해서만 알아보도록 하겠습니다.&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;Zip&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4x9lx/btrBTAc899d/4Qd8BduqH8amVg4UpAoWIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4x9lx/btrBTAc899d/4Qd8BduqH8amVg4UpAoWIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4x9lx/btrBTAc899d/4Qd8BduqH8amVg4UpAoWIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4x9lx%2FbtrBTAc899d%2F4Qd8BduqH8amVg4UpAoWIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;238&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 알아볼 Publisher는 Zip입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보니 2개의 Upstream Publisher를 zip 해서 만들어진 Publisher라고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;zip(_ other:)&lt;/h3&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;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCrqCn/btrBTAjTrfm/boCc89oztpZFj5TvYnTrVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCrqCn/btrBTAjTrfm/boCc89oztpZFj5TvYnTrVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCrqCn/btrBTAjTrfm/boCc89oztpZFj5TvYnTrVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCrqCn%2FbtrBTAjTrfm%2FboCc89oztpZFj5TvYnTrVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;279&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zip Publisher를 활용해서 만든 첫 번째 Operator는 zip(_ other:)입니다. 정의를 보면 2개의 Publisher의 값을 튜플로 만들어서 Downstream으로 전달한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜플로 만든다고 하니까 예전에 알아본 CombineLatest에서도 비슷한 걸 본 거 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 사용해보고 차이점을 살펴봐야겠네요.&lt;/p&gt;
&lt;pre id=&quot;code_1652274458361&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()

firstPublisher
    .zip(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(1)
secondPublisher.send(11)

firstPublisher.send(2)
secondPublisher.send(12)

firstPublisher.send(3)
firstPublisher.send(4)

// zip(_ other:) 예제 코드
(1, 11)
(2, 12)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 얼핏 보면 CombineLatest와 별 차이가 없어 보이는데요, 결과를 보면 차이를 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 firstPublisher가 3, 4를 내보냈을 땐 Downstream으로 전달된 것이 없는데요, 이는 secondPublisher가 내보낸 값은 이미 모두 처리했기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;firstPublisher가 값을 4개 보냈더라도 secondPublisher가 값을 2개만 보냈기 때문에 downstream으로 전달된 튜플은 2개뿐인 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLatest는 처리하는 Publisher들에게 하나 이상의 값을 받은 이후에는 어떤 Publisher에게 값을 받아도 모든 Publisher의 가장 최신 값을 가지고 처리했었습니다. 즉 위 코드에서 zip이 아닌 combineLatest를 사용했다면 (3, 12), (4, 12)도 downstream으로 전달됐을 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zip의 작동방식을 쉽게 이해하려면 배열과 index 개념을 생각하면 됩니다. 각각의 Publisher들에게 받은 값은 순서대로 각각 배열에 쌓이고 동일한 index의 값이 모두 존재할 때 Downstream으로 튜플을 전달한다고 생각하면 이해가 잘 됩니다.(저는 그랬어요  )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 방금 코드에서 3, 4를 Downstream으로 보내기 위해서는 아래와 같이 secondPublisher도 값을 2개 더 보내주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652285936643&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()

firstPublisher
    .zip(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(1)
secondPublisher.send(11)

firstPublisher.send(2)
secondPublisher.send(12)

firstPublisher.send(3)
firstPublisher.send(4)

secondPublisher.send(13)
firstPublisher.send(completion: .finished) // firstPublisher가 내보낸 4는 아직 남아있음.
secondPublisher.send(14) // 모든 값이 처리되어 finished 됨

// zip(_ other:) 예제 코드
(1, 11)
(2, 12)
(3, 13)
(4, 14)
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;completion의 경우 처리하는 모든 Publisher가 finished를 보내면 바로 끝나지만, 위와 같이 하나만 finished를 보낸 경우엔&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;처리하는 모든 Publisher에게 받은 값이 처리된 이후에 finished가 출력되는 걸 볼 수 있습니다.&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;letter-spacing: 0px;&quot;&gt;즉 Publisher finished를 전달하더라도 해당 Publisher가 이미 내보낸 값들은 모두 Downstream으로 전달될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;zip(_ other:, _ transform:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cunBez/btrBUHbJUlL/n21basnV3A5MVDFbkJ4L2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cunBez/btrBUHbJUlL/n21basnV3A5MVDFbkJ4L2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cunBez/btrBUHbJUlL/n21basnV3A5MVDFbkJ4L2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcunBez%2FbtrBUHbJUlL%2Fn21basnV3A5MVDFbkJ4L2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;644&quot; height=&quot;272&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으론 zip(_ other:, _ transform:)을 알아봅시다. 아까 알아본 zip(_ other:)은 튜플만 downstream으로 보낼 수 있었는데요, zip(_other:, _transform:)은 transform에 전달하는 클로저를 활용해서 다른 타입도 전달할 수 있게 해 줍니다.&lt;/p&gt;
&lt;p 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_1652286700010&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;String, Never&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;String, Never&amp;gt;()

firstPublisher
    .zip(secondPublisher) { value1, value2 in
        return value1 + &quot; &quot; + value2
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(&quot;I'm First&quot;)
secondPublisher.send(&quot;I'm Second&quot;)

firstPublisher.send(&quot;I'm First!&quot;)
firstPublisher.send(&quot;I'm First!!&quot;)

secondPublisher.send(&quot;I'm Second!&quot;)

firstPublisher.send(completion: .finished) // firstPublisher가 내보낸 &quot;I'm First!!&quot;는 아직 남아있음

secondPublisher.send(&quot;Bye~&quot;) // 모든 값이 처리되어 finished 됨

// zip(_ other:, _ transform:) 예제 코드
I'm First I'm Second
I'm First! I'm Second!
I'm First!! Bye~
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 Publisher에게 받은 값을 원하는 형태로 Downstream에 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 completion을 처리하는 방법도 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zip3, Zip4의 경우 Zip과 처리하는 Publisher의 개수만 다르고 모두 동일하게 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Combining Elements from Multiple Publishers로 분류된 것들 중 Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers 역할을 하는 Zip 시리즈를 알아봤습니다.&lt;/p&gt;
&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://icksw.tistory.com/287&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Republishing Elements by Subscribing to New Publishers로 분류된 것들에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Collecting%20and%20Republishing%20the%20Oldest%20Unconsumed%20Elements%20from%20Multiple%20Publishers.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>IOS</category>
      <category>Swift</category>
      <category>Zip</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/286</guid>
      <comments>https://icksw.tistory.com/286#entry286comment</comments>
      <pubDate>Thu, 12 May 2022 01:39:47 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Combining Elements from Multiple Publishers 2 - Operator 공부 9</title>
      <link>https://icksw.tistory.com/285</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Publisher 중 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 것들 중 &amp;nbsp;Collecting and Republishing the Latest Elements from Multiple Publishers 역할을 하는 CombineLatest 시리즈를 알아봤었습니다. 여러 개의 Publisher에서 받은 최신 값을 적절히 처리해서 Downstream으로 전달하는 역할을 했었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이어서 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 것들 중 Republishing Elements from Multiple Publishers as an Interleaved Stream 역할을 하는 Merge 시리즈를 알아보도록 하겠습니다.&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;Republishing Elements from Multiple Publishers as an Interleaved Stream&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공부할 Operator를 분류한 이름을 보다보니&amp;nbsp;Interleave의 뜻을 몰라서 뭔지 좀 알아봤더니...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pk1NH/btrBMg7qLI5/zikGAkbdZFcy5T3l6MHQh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pk1NH/btrBMg7qLI5/zikGAkbdZFcy5T3l6MHQh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pk1NH/btrBMg7qLI5/zikGAkbdZFcy5T3l6MHQh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPk1NH%2FbtrBMg7qLI5%2FzikGAkbdZFcy5T3l6MHQh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;712&quot; height=&quot;329&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 뜻이 있네요. 즉 여러개의 Publisher들을 하나의 Stream처럼 사용할 수 있게 해 준다는 뜻이겠네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Merge&lt;/li&gt;
&lt;li&gt;Merge3&lt;/li&gt;
&lt;li&gt;Merge4&lt;/li&gt;
&lt;li&gt;Merge5&lt;/li&gt;
&lt;li&gt;Merge6&lt;/li&gt;
&lt;li&gt;Merge7&lt;/li&gt;
&lt;li&gt;Merge8&lt;/li&gt;
&lt;li&gt;MergeMany&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Publisher의 이름을 보면 모두 Merge라는 이름입니다. &lt;a href=&quot;https://icksw.tistory.com/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;저번 글&lt;/a&gt;에서 봤듯 뒤에 숫자가 늘어나는건 합치는 Publisher의 개수만 늘어나는 차이만 있어서 이번 글에서는 Merge, MergeMany만 보려고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;merge(with other: P)&lt;/li&gt;
&lt;li&gt;merge(with b: B, _ c: C)&lt;/li&gt;
&lt;li&gt;merge(with b: B, _ c: C, _ d: D)&lt;/li&gt;
&lt;li&gt;merge(with b: B, _ c: C, _ d: D, _ e: E)&lt;/li&gt;
&lt;li&gt;merge(with b: B, _ c: C, _ d: D, _ e: E, _ f: F)&lt;/li&gt;
&lt;li&gt;merge(with b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G)&lt;/li&gt;
&lt;li&gt;merge(with b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h : H)&lt;/li&gt;
&lt;li&gt;merge(with other: Self)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Operator는 위와 같이 모두 이름이 merge입니다. 아까 언급한 대로 대부분은 Publisher의 개수만 다르기 때문에 이번 글에서는 merge(with other: P), merge(with other: Self)만 알아보도록 하겠습니다.&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;Merge&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7awN/btrBw97mg94/Wh60dwk3EjyvvKLRGBOYQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7awN/btrBw97mg94/Wh60dwk3EjyvvKLRGBOYQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7awN/btrBw97mg94/Wh60dwk3EjyvvKLRGBOYQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7awN%2FbtrBw97mg94%2FWh60dwk3EjyvvKLRGBOYQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;254&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 알아볼 Publisher는 Merge 입니다. 정의를 보면 2개의 Upstream Publisher를 합쳐주는 역할을 하는 Publisher라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge3, Merge4... 등등은 위 정의에서 Upstream Publisher의 개수만 다릅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;merge(with other: P)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biJDGd/btrBLYlBRgg/Hmh7ah3VInOyvLAgUC5AT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biJDGd/btrBLYlBRgg/Hmh7ah3VInOyvLAgUC5AT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biJDGd/btrBLYlBRgg/Hmh7ah3VInOyvLAgUC5AT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiJDGd%2FbtrBLYlBRgg%2FHmh7ah3VInOyvLAgUC5AT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;271&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Publisher를 활용해서 만든 Operator는 merge(with other: P) 입니다. 이게 이후에 알아볼 merge(with other: Self)와 생김새가 비슷해서 헷갈릴 수 있는데, 여기서 매개변수로 활용하는 타입은 자기 자신과 Failure, Output 타입이 같은 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 정의를 보면 현재 Publisher와 합친 다른 Publisher가 내보내는 값을 Downstream에 전달한다고 되어있네요.&lt;/p&gt;
&lt;p 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_1652187654138&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()

firstPublisher
    .merge(with: secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(1)
secondPublisher.send(11)
firstPublisher.send(2)
secondPublisher.send(12)

// merge(with:) 예제 코드
1
11
2
12&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;merge(with other: P)는 Upstream의 2개의 Publisher가 값을 내보낼 때마다 새로운 값을 받습니다. 그러니까 그냥 간단하게 2개의 Publisher를 각각 subscribe하는 것과 비슷한 효과를 낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MergeMany&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BNjcP/btrBuAq4T6b/VPJCJ8zsZhaJZDNObZKBRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BNjcP/btrBuAq4T6b/VPJCJ8zsZhaJZDNObZKBRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BNjcP/btrBuAq4T6b/VPJCJ8zsZhaJZDNObZKBRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBNjcP%2FbtrBuAq4T6b%2FVPJCJ8zsZhaJZDNObZKBRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;238&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 MergeMany 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 여러 개의 Publisher를 합치는 역할을 한다고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;merge(with other: Self)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chMfb3/btrBOvoXxGM/DWxfreINW2uidmzRgfHIhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chMfb3/btrBOvoXxGM/DWxfreINW2uidmzRgfHIhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chMfb3/btrBOvoXxGM/DWxfreINW2uidmzRgfHIhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchMfb3%2FbtrBOvoXxGM%2FDWxfreINW2uidmzRgfHIhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;252&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MergeMany Publisher를 활용해서 만든 Operator는 merge(with other: Self) 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 동일한 타입의 Publisher를 합쳐서 내보내는 모든 값을 전달하는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 Operator는 아까 알아본 merge(with other: P)와 동일하게 사용하면 되므로 넘어가고 MergeMany의 존재 이유를 알기 위한 예제를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge 시리즈는 Merge8 까지 있었는데요 8개보다 더 많은 Publisher를 한 번에 처리하고 싶을 때 MergeMany를 사용하면 됩니다.&lt;/p&gt;
&lt;p 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_1652188142996&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var publishers: [PassthroughSubject&amp;lt;Int, Never&amp;gt;] = []

for _ in 0...20 {
    let publisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
    publishers.append(publisher)
}

Publishers.MergeMany(publishers)
	.sink(receiveValue: { print($0, terminator: &quot; &quot;) })

for index in 0..&amp;lt;publishers.count {
    publishers[index].send(index)
}

// MergeMany 예제 코드
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 무려 20개의 Publisher를 하나처럼 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge 시리즈는 여러 개의 Publisher가 내보내는 값을 순서에 상관없이 처리하고 싶을 때 사용하면 좋을 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 것들 중 Republishing Elements from Multiple Publishers as an Interleaved Stream 역할을 하는 Merge 시리즈에 대해 알아봤습니다.&lt;/p&gt;
&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://icksw.tistory.com/286&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 Publisher 중에서 Collecting and Republishing the Oldest Uncomsumed Elements from Multiple Publishers 역할을 하는 Zip 시리즈에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Republishing%20Elements%20from%20Multiple%20Publishers%20as%20an%20Interleaved%20Stream.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>IOS</category>
      <category>merge</category>
      <category>MergeMany</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/285</guid>
      <comments>https://icksw.tistory.com/285#entry285comment</comments>
      <pubDate>Tue, 10 May 2022 22:32:09 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Combining Elements from Multiple Publishers 1 - Operator 공부 8</title>
      <link>https://icksw.tistory.com/284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/283&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Selecting Specific Elements로 분류된 것들을 알아봤었습니다. Upstream에서 받은 값들 중 특정 값만 Downstream으로 전달하는 역할을 했었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 Publisher를 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers역할을 하는 Publisher는 아래와 같은 역할로 나눌 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Collecting and Republishing the Latest Elements from Multiple Publishers (CombineLatest)&lt;/li&gt;
&lt;li&gt;Republishing Elements from Multiple Publishers as an Interleaved Stream (Merge)&lt;/li&gt;
&lt;li&gt;Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers (Zip)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 개의 분류로 나뉘어져있으니 하나씩 알아보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 먼저 Collecting and Republishing the Latest Elements from Multiple Publishers 역할을 하는 CombineLatest 시리즈를 알아보도록 하겠습니다.&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;Collecting and Republishing the Latest Elements from Multiple Publishers&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공부할 Operator들을 분류한 이름을 보니 여러 개의 Publisher의 값을 모으거나 최신 값을 Downstream으로 보내주는 역할을 할 거 같네요. 그럼 먼저 여기에 분류된 Publisher에는 어떤 것들이 있는지부터 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CombineLatest&lt;/li&gt;
&lt;li&gt;CombineLatest3&lt;/li&gt;
&lt;li&gt;CombineLatest4&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 Publisher들을 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;other:,&amp;nbsp;_&amp;nbsp;transform:)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;other:)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_ publisher2:,&amp;nbsp;_&amp;nbsp;transform:)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_&amp;nbsp;publisher2:)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_ publisher2:,&amp;nbsp;_&amp;nbsp;publisher3:,&amp;nbsp;_&amp;nbsp;transform:)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_ publisher 2:,&amp;nbsp;_&amp;nbsp;publisher3:)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher도 그렇고 Operator도 그렇고 모두 이름이 combineLatest네요. 그래서 헷갈리긴 한데.. 가만히 보면 규칙이 있습니다. 일단 Publisher의 경우 combineLatest, combineLatest3, combineLatest4와 같이 숫자가 증가하는데, 아까 분류된 이름에 Multiple Publisher가 있었는데 여기서 숫자가 아마 몇 개의 Publisher를 처리할 수 있는지를 나타내는 거 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operator의 경우엔 그에 따라 매개변수로 받는 publisher의 개수가 하나씩 늘어나는 걸 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 알아보도록 할게요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CombineLatest&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHrPb5/btrBs0bPtR8/4u3k6TCvokkQqS6W9bd7a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHrPb5/btrBs0bPtR8/4u3k6TCvokkQqS6W9bd7a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHrPb5/btrBs0bPtR8/4u3k6TCvokkQqS6W9bd7a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHrPb5%2FbtrBs0bPtR8%2F4u3k6TCvokkQqS6W9bd7a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;256&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 알아볼 Publisher는 CombineLatest입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 2개의 Publisher로 값을 받아서 최신 값을 내려보내는 Publisher라고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;other:,&amp;nbsp;_ transform:)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ofe8A/btrBrq3qka6/XjuS7ev1O4InhP4EA4o29K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ofe8A/btrBrq3qka6/XjuS7ev1O4InhP4EA4o29K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ofe8A/btrBrq3qka6/XjuS7ev1O4InhP4EA4o29K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fofe8A%2FbtrBrq3qka6%2FXjuS7ev1O4InhP4EA4o29K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;805&quot; height=&quot;381&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLatest를 활용해서 만든 첫 번째 Operator는 combineLatest(_ other:, _ transform:)입니다. 공식문서에 정의가 이상하게 코드로 되어있네요 ㅋㅋㅋ.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수로 받은 other라는 Publisher는 현재 upstream 역할을 하는 Publisher와 결합할 Publisher이고 transform 클로저는 두 개의 publisher에게 최신 값을 받았을 때 downstream으로 두 개의 값을 적절히 처리하여 내려보내는 기능을 합니다.&lt;/p&gt;
&lt;p 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_1651848485042&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()

firstPublisher
    .combineLatest(secondPublisher) { value1, value2 in
        return value1 + value2
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(&quot;First &quot;)
secondPublisher.send(&quot;Second&quot;)

secondPublisher.send(&quot;Second-2&quot;)
firstPublisher.send(&quot;First-2 &quot;)

secondPublisher.send(completion: .finished)
firstPublisher.send(completion: .finished)

// combineLatest(_ other:, _ transform:) 예제 코드 결과
First Second
First Second-2
First-2 Second-2
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 두 개의 Publisher 모두가 값을 내보내기 전에는 Downstream에 아무것도 전달되지 않습니다. 즉 두 개의 Publisher에서 값을 하나 이상 받은 이후부터 값을 받을 때마다 downstream에 값이 전달되는 걸 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 두 개의 Publisher가 모두 complete 되어야 finish 되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;other:)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjJbjX/btrBqushntX/6QFherNdgj0JfZMRnga6l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjJbjX/btrBqushntX/6QFherNdgj0JfZMRnga6l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjJbjX/btrBqushntX/6QFherNdgj0JfZMRnga6l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjJbjX%2FbtrBqushntX%2F6QFherNdgj0JfZMRnga6l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;467&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLatest를 활용해서 만든 두 번째 Operator는 combineLatest(_ other:)입니다. 정의를 보면 2개의 Publisher에서 값을 하나 이상 받으면 각각의 Publisher가 내보낸 값 중 최신 값 쌍을 튜플 타입으로 Downstream으로 내려보내는 역할을 한다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 알아본 combineLatest(_ other:, _ transform:)에서는 값을 직접 다룰 수 있었는데, 이번 건 튜플 타입으로만 내려보낼 수 있습니다. 공통점은 두 개의 Publisher가 모두 complete 되어야 complete 된다는 점입니다.&lt;/p&gt;
&lt;p 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_1651849402491&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()

firstPublisher
    .combineLatest(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(&quot;First &quot;)
secondPublisher.send(&quot;Second&quot;)

firstPublisher.send(&quot;First-2&quot;)

secondPublisher.send(completion: .finished)
firstPublisher.send(completion: .finished)

// combineLatest(_ other:) 예제 코드 결과
(&quot;First &quot;, &quot;Second&quot;)
(&quot;First-2&quot;, &quot;Second&quot;)
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 Publisher가 내보낸 값을 튜플 타입으로 잘 내려보내는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CombineLatest3&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G4Dt7/btrBrrOQPGo/j8A4RJrZlxe4Fwrhlfr5gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G4Dt7/btrBrrOQPGo/j8A4RJrZlxe4Fwrhlfr5gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G4Dt7/btrBrrOQPGo/j8A4RJrZlxe4Fwrhlfr5gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG4Dt7%2FbtrBrrOQPGo%2Fj8A4RJrZlxe4Fwrhlfr5gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;236&quot; data-origin-width=&quot;964&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;다음으로 알아볼 Publisher는 CombineLatest3입니다. 아까 알아본 CombineLatest가 처리하는 Publisher의 수가 2개였다면 CombineLatest3는 3개의 Publisher의 값을 처리해서 Downstream으로 내려보냅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_ publisher2:,&amp;nbsp;_&amp;nbsp;transform:)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxwc2n/btrBrtTGhGl/H9yqDZX05mqCKMHcZukpa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxwc2n/btrBrtTGhGl/H9yqDZX05mqCKMHcZukpa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxwc2n/btrBrtTGhGl/H9yqDZX05mqCKMHcZukpa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxwc2n%2FbtrBrtTGhGl%2FH9yqDZX05mqCKMHcZukpa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;532&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;828&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;CombineLatest3을 활용해서 만든 첫 번째 Publisher는 combineLatest(_ publisher1:, _ publisher2:, _ transform:)입니다. 아까 알아본 combineLatest(_ other:, _ transform:)과 동일한데 Publisher가 하나 더 늘어난 차이밖에 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 세 개의 Publisher에서 값을 하나 이상 받은 순간부터 Downstream으로 값을 내려보내며, 3개의 publisher가 모두 complete 되어야 finish 됩니다.&lt;/p&gt;
&lt;p 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_1651850456314&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()
let thirdPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()

firstPublisher
    .combineLatest(secondPublisher, thirdPublisher) { value1, value2, value3 in
        return value1 + value2 + value3
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(&quot;First &quot;)
secondPublisher.send(&quot;Second &quot;)
thirdPublisher.send(&quot;Third&quot;)

secondPublisher.send(&quot;Second-2 &quot;)

firstPublisher.send(completion: .finished)
secondPublisher.send(completion: .finished)

thirdPublisher.send(&quot;Third-2&quot;)
thirdPublisher.send(completion: .finished)

// combineLatest(_ publisher1:, _ publisher2:, _ transform:)  예제 코드 결과
First Second Third
First Second-2 Third
First Second-2 Third-2
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 간단하게 사용할 수 있습니다. 위 코드를 보면 firstPublisher, secondPublisher가 completion을 전달한 이후 thirdPublisher에서 값을 내보냈을 때도 각각의 Publisher의 최신 값을 처리해서 Downstream으로 전달한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_ publisher2:)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMPWRt/btrBq7pNgC9/i2RDKZsW8lZWCgFvk2xkD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMPWRt/btrBq7pNgC9/i2RDKZsW8lZWCgFvk2xkD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMPWRt/btrBq7pNgC9/i2RDKZsW8lZWCgFvk2xkD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMPWRt%2FbtrBq7pNgC9%2Fi2RDKZsW8lZWCgFvk2xkD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;615&quot; height=&quot;411&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLatest3을 활용해서 만든 두 번째 Publisher는 combineLatest(_ publisher1:, _ publisher2:) 입니다. 얘는 3개의 Publisher에게서 하나 이상의 값을 받은 이후부터 각각의 Publisher에서 받은 최신 값을 튜플로 만들어서 Downstream에 전달하는 역할을 합니다.&lt;/p&gt;
&lt;p 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_1651850693837&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()
let thirdPublisher = PassthroughSubject&amp;lt;String, Error&amp;gt;()

firstPublisher
    .combineLatest(secondPublisher, thirdPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(&quot;First &quot;)
secondPublisher.send(&quot;Second &quot;)
thirdPublisher.send(&quot;Third&quot;)

secondPublisher.send(&quot;Second-2 &quot;)

firstPublisher.send(completion: .finished)
secondPublisher.send(completion: .finished)

thirdPublisher.send(&quot;Third-2&quot;)
thirdPublisher.send(completion: .finished)

// combineLatest(_ publisher1:, _ publisher2:)  예제 코드 결과
(&quot;First &quot;, &quot;Second &quot;, &quot;Third&quot;)
(&quot;First &quot;, &quot;Second-2 &quot;, &quot;Third&quot;)
(&quot;First &quot;, &quot;Second-2 &quot;, &quot;Third-2&quot;)
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CombineLatest4&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UpWng/btrBqu0aihA/CywrZMKqFws0vFk6DUrkDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UpWng/btrBqu0aihA/CywrZMKqFws0vFk6DUrkDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UpWng/btrBqu0aihA/CywrZMKqFws0vFk6DUrkDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUpWng%2FbtrBqu0aihA%2FCywrZMKqFws0vFk6DUrkDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;270&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 알아볼 Publisher는 CombineLatest4입니다. 아마 예상하셨을 텐데, 얘는 4개의 Publisher의 값을 처리해줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1:,&amp;nbsp;_ publisher2:,&amp;nbsp;_&amp;nbsp;publisher3:,&amp;nbsp;_&amp;nbsp;transform:)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmoyc5/btrBrr9algx/Z8f5R0KLoe1oKd5jkoyj4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmoyc5/btrBrr9algx/Z8f5R0KLoe1oKd5jkoyj4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmoyc5/btrBrr9algx/Z8f5R0KLoe1oKd5jkoyj4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmoyc5%2FbtrBrr9algx%2FZ8f5R0KLoe1oKd5jkoyj4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;824&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLatest4를 활용해서 만든 첫 번째 Operator는 combineLatest(_ publisher1, _ publisher2, _ publisher3, _ transform:)입니다. 아까 알아본 combineLatest(_ other:, _ transform:), combineLatest(_ publisher1:, _ publisher2:, _ transform:)과 동일하게 동작하지만 이번에는 publisher가 4개라는 차이점만 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher가 4개라는 점을 제외하고는 모두 동일하니 사용 코드는 넘어가도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 혹시라도 궁금하시다면 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Collecting%20and%20Republishing%20the%20Latest%20Elements%20from%20Multiple%20Publishers.playground/Contents.swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;combineLatest(_&amp;nbsp;publisher1: ,&amp;nbsp;_ publisher2:,&amp;nbsp;_&amp;nbsp;publisher3:)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tKeFR/btrBrKHD0M5/4eeRkSw9PB8A8klko78INk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tKeFR/btrBrKHD0M5/4eeRkSw9PB8A8klko78INk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tKeFR/btrBrKHD0M5/4eeRkSw9PB8A8klko78INk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtKeFR%2FbtrBrKHD0M5%2F4eeRkSw9PB8A8klko78INk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1548&quot; height=&quot;770&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;CombineLatest4를 활용해서 만든 두 번째 Operator는 combineLatest(_ publisher1:, _ publisher2:, _ publisher3:)입니다.&amp;nbsp;얘도 아까 알아본 combineLatest(_ other:), combineLatest(_ publisher1:, _ publisher2:)와 동일한데 이번에는 Publisher가 4개라는 차이점만 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Publisher가 4개라는 점을 제외하고는 모두 동일하니 사용 코드는 넘어가도록 할게요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 혹시라도 궁금하시다면 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Collecting%20and%20Republishing%20the%20Latest%20Elements%20from%20Multiple%20Publishers.playground/Contents.swift&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Combine의 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 것들 중 Collecting and Republishing the Latest Elements from Multiple Publishers 역할을 하는 CombineLatest 시리즈를 알아봤습니다. 사실 Publisher의 개수만 차이나고 모두 동일한 동작 방식을 가지고 있어서 크게 어렵진 않았던 거 같습니다.&lt;/p&gt;
&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://icksw.tistory.com/285&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Combining&amp;nbsp;Elements&amp;nbsp;from&amp;nbsp;Multiple&amp;nbsp;Publishers로 분류된 것들 중 Republishing Elements from Multiple Publishers as an Interleaved Stream 역할을 하는 Merge 시리즈에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Collecting%20and%20Republishing%20the%20Latest%20Elements%20from%20Multiple%20Publishers.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>combineLatest</category>
      <category>combineLatest3</category>
      <category>combineLatest4</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/284</guid>
      <comments>https://icksw.tistory.com/284#entry284comment</comments>
      <pubDate>Sat, 7 May 2022 00:52:21 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Selecting Specific Elements - Operator 공부 7</title>
      <link>https://icksw.tistory.com/283</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/282?category=937485&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Applying Sequence Operations to Elements로 분류된 것들을 알아봤습니다. Sequence에 존재하는 몇몇 메서드와 비슷한 기능으로 Upstream에서 받은 값을 처리해서 Downstream으로 전달하는 역할을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Selecting Specific Elements로 분류된 Operator에 대해서 알아보도록 하겠습니다.&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;Selecting Specific Elements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공부할 것들을 분류해둔 이름을 보니 뭔가 특정한 값을 선택해서 Downstream으로 보내줄 것 같습니다. 그럼 먼저 여기에 분류된 Publisher에는 어떤 것들이 있는지부터 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;First&lt;/li&gt;
&lt;li&gt;FirstWhere&lt;/li&gt;
&lt;li&gt;TryFirstWhere&lt;/li&gt;
&lt;li&gt;Last&lt;/li&gt;
&lt;li&gt;LastWhere&lt;/li&gt;
&lt;li&gt;TryLastWhere&lt;/li&gt;
&lt;li&gt;Output&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 Publisher들을 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;first()&lt;/li&gt;
&lt;li&gt;first(where:)&lt;/li&gt;
&lt;li&gt;tryFirst(where:)&lt;/li&gt;
&lt;li&gt;last()&lt;/li&gt;
&lt;li&gt;last(where:)&lt;/li&gt;
&lt;li&gt;tryLast(where:)&lt;/li&gt;
&lt;li&gt;output(at:)&lt;/li&gt;
&lt;li&gt;output(in:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 어떤 역할을 하는 것들인지 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;First&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lR1uW/btrAUx9bjX6/ETnaU7hzuP7I1r5fmm2gY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lR1uW/btrAUx9bjX6/ETnaU7hzuP7I1r5fmm2gY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lR1uW/btrAUx9bjX6/ETnaU7hzuP7I1r5fmm2gY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlR1uW%2FbtrAUx9bjX6%2FETnaU7hzuP7I1r5fmm2gY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;148&quot; data-origin-width=&quot;487&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;첫 번째로 알아볼 Publisher는 First입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름에서 느껴지는 대로 Publisher가 받은 첫 번째 값을 내려보내고 finish 되는 Publisher입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;first()&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTP8Fk/btrASuSVMTg/Qb8vAlmy1DfMrBCa0YRc5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTP8Fk/btrASuSVMTg/Qb8vAlmy1DfMrBCa0YRc5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTP8Fk/btrASuSVMTg/Qb8vAlmy1DfMrBCa0YRc5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTP8Fk%2FbtrASuSVMTg%2FQb8vAlmy1DfMrBCa0YRc5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;243&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;First를 활용해서 만든 Operator는 first()입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 첫 번째 값을 내려보내고 finish 되는 녀석이라고 하네요.&lt;/p&gt;
&lt;p 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_1651308536403&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisehr = [1, 2, 3, 4].publisher

intPublisehr
    .first()
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// first() 예제 코드
1
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;FirstWhere&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s9LjV/btryyA82JsM/xlzn9lDP039KKVeeKf8XX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s9LjV/btryyA82JsM/xlzn9lDP039KKVeeKf8XX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s9LjV/btryyA82JsM/xlzn9lDP039KKVeeKf8XX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs9LjV%2FbtryyA82JsM%2Fxlzn9lDP039KKVeeKf8XX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;148&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 FirstWhere입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 주어진 클로저를 만족하는 첫 번째 값만 내려보내는 Publisher라고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;first(where:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UhhzJ/btrATTEGK7l/5rEwis1dzKeu7e7O0OBAgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UhhzJ/btrATTEGK7l/5rEwis1dzKeu7e7O0OBAgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UhhzJ/btrATTEGK7l/5rEwis1dzKeu7e7O0OBAgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUhhzJ%2FbtrATTEGK7l%2F5rEwis1dzKeu7e7O0OBAgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;285&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FirstWhere를 활용해서 만들어진 Operator는 first(where:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 클로저를 만족하는 첫 번째 값만 내려보내고 finish 된다고 하네요.&lt;/p&gt;
&lt;p 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_1651308733251&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 3, 2, 4].publisher

intPublisher
    .first(where: { value in
        return value % 2 == 0
    })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// first(where:) 예제 코드
2
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryFirstWhere&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xCjf9/btrATdJ8uWR/4ztxa9nHQiP7q6y8mFpZD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xCjf9/btrATdJ8uWR/4ztxa9nHQiP7q6y8mFpZD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xCjf9/btrATdJ8uWR/4ztxa9nHQiP7q6y8mFpZD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxCjf9%2FbtrATdJ8uWR%2F4ztxa9nHQiP7q6y8mFpZD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;710&quot; height=&quot;175&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 아까 알아본 FirstWhere의 try 버전입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 FirstWhere이랑 똑같은 역할을 하면서 에러를 던질 수 있는 녀석입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryFirst(where:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKhM5Z/btrATSZ4dE2/JTavuXuSTDZWL3yIQTY6k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKhM5Z/btrATSZ4dE2/JTavuXuSTDZWL3yIQTY6k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKhM5Z/btrATSZ4dE2/JTavuXuSTDZWL3yIQTY6k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKhM5Z%2FbtrATSZ4dE2%2FJTavuXuSTDZWL3yIQTY6k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;283&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryFirstWhere를 활용해서 만든 Operator는 tryFirst(where:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 얘도 예상대로 first(where:)과 동일하게 주어진 클로저를 만족하는 첫 번째 값만 내려보내고 finish 되는데, 에러를 던질 수도 있는 녀석입니다.&lt;/p&gt;
&lt;p 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_1651308982281&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPubisher = [1, -1, 2, 3].publisher

intPubisher
    .tryFirst(where: { value in
        guard value &amp;lt; 0 else { throw PinguError() }
        return value % 2 == 0
    })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// tryFirst(where:) 예제 코드
failure(__lldb_expr_8.(unknown context at $10291f3bc).(unknown context at $10291f3c4).(unknown context at $10291f3cc).PinguError())&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Last&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UeG9j/btrAPAUdmtR/gfn94L6Xgguq44KFIb2YZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UeG9j/btrAPAUdmtR/gfn94L6Xgguq44KFIb2YZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UeG9j/btrAPAUdmtR/gfn94L6Xgguq44KFIb2YZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUeG9j%2FbtrAPAUdmtR%2Fgfn94L6Xgguq44KFIb2YZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;174&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 Last입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Last는 방금 알아본 First와는 다르게 Upstream에서 받은 값 중 가장 마지막 값을 내려보내는 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream에게 finish를 전달받으면 바로 직전에 받은 값을 내려보내게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;last()&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oZn7e/btrAUx9b1Sf/YbHRbNJZX874gcWJncdta1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oZn7e/btrAUx9b1Sf/YbHRbNJZX874gcWJncdta1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oZn7e/btrAUx9b1Sf/YbHRbNJZX874gcWJncdta1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoZn7e%2FbtrAUx9b1Sf%2FYbHRbNJZX874gcWJncdta1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;245&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Last Publisher를 활용해서 만든 Operator는 last()입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream에서 받은 값 중 가장 마지막에 받은 값만 Downstream으로 내려보내는 역할을 합니다.&lt;/p&gt;
&lt;p 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_1651309141496&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisehr = [1, 2, 3, 4].publisher

intPublisehr
    .last()
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// last() 예제 코드
4
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LastWhere&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/50Lod/btrATehZ5dL/ul8RwcLricHk1piDyAOYNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/50Lod/btrATehZ5dL/ul8RwcLricHk1piDyAOYNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/50Lod/btrATehZ5dL/ul8RwcLricHk1piDyAOYNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F50Lod%2FbtrATehZ5dL%2Ful8RwcLricHk1piDyAOYNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;158&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LastWhere Publisher는 주어진 클로저를 만족하는 값들 중 가장 마지막 값을 내려보내는 Publisher입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;last(where:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSVCy9/btrAQ6enZBp/spubogKEB8rZ72LHBEarRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSVCy9/btrAQ6enZBp/spubogKEB8rZ72LHBEarRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSVCy9/btrAQ6enZBp/spubogKEB8rZ72LHBEarRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSVCy9%2FbtrAQ6enZBp%2FspubogKEB8rZ72LHBEarRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;294&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LastWhere Publisher를 활용해서 만든 Operator는 last(where:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream에서 받은 값 중에 클로저를 만족시키는 가장 마지막 값만 Downstream으로 내려보내는 역할을 합니다.&lt;/p&gt;
&lt;p 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_1651309490273&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 5, 7, 3, 4, 9, 2].publisher

intPublisher
    .last(where: { value in
        return value &amp;gt; 4
    })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// last(where:) 예제 코드
9
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 Upstream에서 받은 값 중 4보다 큰 가장 마지막 값만 내려보내는 코드이므로 9만 Downstream으로 전달된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryLastWhere&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAMYZr/btrASatBX3H/dfBW1n65YiluAdUtTQ9USK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAMYZr/btrASatBX3H/dfBW1n65YiluAdUtTQ9USK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAMYZr/btrASatBX3H/dfBW1n65YiluAdUtTQ9USK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAMYZr%2FbtrASatBX3H%2FdfBW1n65YiluAdUtTQ9USK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;145&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 LastWhere의 try 버전인 TryLastWhere입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LastWhere과 마찬가지로 클로저를 만족하는 가장 마지막 값을 내려보내며 에러를 던질 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryLastWhere(where:)&lt;/h3&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;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PyMUi/btrASaNVbTs/mTXPkQ9POv0qCXvEOsFKc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PyMUi/btrASaNVbTs/mTXPkQ9POv0qCXvEOsFKc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PyMUi/btrASaNVbTs/mTXPkQ9POv0qCXvEOsFKc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPyMUi%2FbtrASaNVbTs%2FmTXPkQ9POv0qCXvEOsFKc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;287&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryLastWhere을 활용해서 만든 Operator는 tryLast(where:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;last(where:)과 동일하게 클로저를 만족하는 값 중 마지막 값을 Downstream으로 전달하며 에러도 던질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 사용해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651309750085&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPublisher = [1, 3, 7, -4, 6, 4].publisher

intPublisher
    .tryLast(where: { value in
        guard value &amp;lt; 0 else { throw PinguError() }
        return value &amp;gt; 5
    })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// tryLast(where:) 예제 코드
failure(__lldb_expr_14.(unknown context at $1039f063c).(unknown context at $1039f067c).(unknown context at $1039f0684).PinguError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 사용할 수 있는데요, 어떻게 보면 모든 값을 어떤 조건으로 확인한다는 점에서 예전에 알아본 allSatisfy()와도 비슷한 부분이 있는 거 같네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Output&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OJBxo/btrARMmduQk/7aWkQ7UA0fuwrADciUIpI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OJBxo/btrARMmduQk/7aWkQ7UA0fuwrADciUIpI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OJBxo/btrARMmduQk/7aWkQ7UA0fuwrADciUIpI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOJBxo%2FbtrARMmduQk%2F7aWkQ7UA0fuwrADciUIpI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;135&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 알아볼 Publisher는 Output입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값들 중 특정 범위로 지정된 값들만 Downstream으로 내려보내는 Publisher라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 범위란 Upstream에서 받은 값들에 순서를 매겨서 특정 순서의 값들만 Downstream으로 내려보내겠다! 이런 의미입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;output(at:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhoOGj/btrARqjolun/NiP4JxSA7wSSlkfaOwsy7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhoOGj/btrARqjolun/NiP4JxSA7wSSlkfaOwsy7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhoOGj/btrARqjolun/NiP4JxSA7wSSlkfaOwsy7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhoOGj%2FbtrARqjolun%2FNiP4JxSA7wSSlkfaOwsy7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;264&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output Publisher를 활용해서 만든 첫 번째 Operator는 output(at:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값들 중 특정 순서의 값만 Downstream으로 전달한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 순서의 값을 만약 받았다면 그 이후의 값은 필요가 없으니 바로 finish 되는 특징도 있습니다.&lt;/p&gt;
&lt;p 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_1651310177247&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [0, 1, 2, 3, 4].publisher

intPublisher
    .output(at: 2)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// output(at:) 예제 코드
2
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;output(in:)&lt;/h3&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;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpN0yF/btrAQ6MgEip/9KwhZ5WnxeRvaqQ3qGCIN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpN0yF/btrAQ6MgEip/9KwhZ5WnxeRvaqQ3qGCIN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpN0yF/btrAQ6MgEip/9KwhZ5WnxeRvaqQ3qGCIN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpN0yF%2FbtrAQ6MgEip%2F9KwhZ5WnxeRvaqQ3qGCIN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;280&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output Publisher를 활용해서 만든 두 번째 Operator는 output(in:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 output(at:)은 특정 순서의 값 하나만 Downstream으로 전달했다면 output(in:)은 특정 순서 범위의 모든 값을 Downstream으로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output(in:)도 특정 범위를 벗어나는 값들은 필요가 없으므로 해당 범위의 마지막 순서의 값을 받으면 finish 됩니다.&lt;/p&gt;
&lt;p 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_1651310332435&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [0, 1, 2, 3, 4, 5, 6].publisher

intPublisher
    .output(in: 3...5)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// output(in:) 예제 코드
3
4
5
finished&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;print()를 활용해서 간단하게 output(in:)이 정말 범위를 초과하는 값은 받지도 않고 finish 되는지 확인해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651310458783&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [0, 1, 2, 3, 4, 5, 6].publisher

intPublisher
    .print()
    .output(in: 3...5)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

// output(in:) 예제 코드
receive subscription: ([0, 1, 2, 3, 4, 5, 6])
request unlimited
receive value: (0)
request max: (1) (synchronous)
receive value: (1)
request max: (1) (synchronous)
receive value: (2)
request max: (1) (synchronous)
receive value: (3)
3
receive value: (4)
4
receive value: (5)
5
receive cancel
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 본 예제에 print()만 추가한 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 5번째 값을 받으니까 receive cancel이 출력되고 finish 되는 것을 볼 수 있습니다.&lt;/p&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의 Publisher와 Operator 중에서 Selecting Specific Elements로 분류된 것들에 대해 알아봤습니다. 비교적 이름들이 직관적이고 간단해서 쉽게 이해할 수 있었던거 같아요.&lt;/p&gt;
&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://icksw.tistory.com/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Combining Elements from Multiple Publishers로 분류된 것들에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/ApplyingSequenceOperationsToElements.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>First</category>
      <category>FirstWhere</category>
      <category>Last</category>
      <category>LastWhere</category>
      <category>output</category>
      <category>Swift</category>
      <category>TryFirstWhere</category>
      <category>TryLastWhere</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/283</guid>
      <comments>https://icksw.tistory.com/283#entry283comment</comments>
      <pubDate>Sat, 30 Apr 2022 18:26:53 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Applying Sequence Operations to Elements - Operator 공부 6</title>
      <link>https://icksw.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Applying Matching Criteria to Elements로 분류된 Operator들을 알아봤었습니다. Upstrem에서 받은 값에 원하는 값이 있는지 혹은 조건에 만족하는 값인지를 확인해서 Bool 값을 Downstream으로 보내는 역할을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이어서 Applying Sequence Operations to Elements로 분류된 Operator에 대해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Applying Sequence Operations to Elements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공부할 녀석들을 분류해둔 이름을 보면 값들에 Sequence 작업을 적용하는 역할을 할 거 같습니다. 그럼 먼저 여기에 분류된 Publisher에는 어떤 것들이 있는지부터 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DropUntilOutput&lt;/li&gt;
&lt;li&gt;Drop&lt;/li&gt;
&lt;li&gt;DropWhile&lt;/li&gt;
&lt;li&gt;TryDropWhile&lt;/li&gt;
&lt;li&gt;Concatenate&lt;/li&gt;
&lt;li&gt;PrefixWhile&lt;/li&gt;
&lt;li&gt;TryPrefixWhile&lt;/li&gt;
&lt;li&gt;PrefixUntilOutput&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 Publisher들을 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;drop(untilOutputFrom:)&lt;/li&gt;
&lt;li&gt;dropFirst(_:)&lt;/li&gt;
&lt;li&gt;drop(while:)&lt;/li&gt;
&lt;li&gt;append(_:)&lt;/li&gt;
&lt;li&gt;prepend(_:)&lt;/li&gt;
&lt;li&gt;prefix(_:)&lt;/li&gt;
&lt;li&gt;prefix(while:)&lt;/li&gt;
&lt;li&gt;tryPrefix(while:)&lt;/li&gt;
&lt;li&gt;prefix(untilOutputFrom:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 어떤 역할들을 하는 것들인지 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DropUntilOutput&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7kXwl/btrAIPjCooK/r4kq6XgwBXpHowqt3FNfzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7kXwl/btrAIPjCooK/r4kq6XgwBXpHowqt3FNfzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7kXwl/btrAIPjCooK/r4kq6XgwBXpHowqt3FNfzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7kXwl%2FbtrAIPjCooK%2Fr4kq6XgwBXpHowqt3FNfzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;171&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;129&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;첫 번째로 알아볼 Publisher는 DropUntilOutput 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 두 번째 Publisher로부터 값을 받을 때까지 Upstream Publisher의 값을 무시하는 Publisher라고 하네요. 이걸 활용해서 만든 Operator를 보면 이해가 빠르게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;drop(untilOutputFrom:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLCMbW/btrAJ65WT4X/irm9vkCG8uxtkuGQ7gkhFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLCMbW/btrAJ65WT4X/irm9vkCG8uxtkuGQ7gkhFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLCMbW/btrAJ65WT4X/irm9vkCG8uxtkuGQ7gkhFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLCMbW%2FbtrAJ65WT4X%2Firm9vkCG8uxtkuGQ7gkhFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;279&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DropUntilOutput Publisher를 활용해서 만든 Operator는 drop(untilOutputFrom:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 두 번째 Publisher에서 값을 받기 전에는 upstream Publisher에서 받는 값을 모두 무시한다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 두 번째 Publisher란 매개변수로 받은 Publisher를 의미합니다.&lt;/p&gt;
&lt;p 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_1651157039430&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let upstreamPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let secondPublisher = PassthroughSubject&amp;lt;String, Never&amp;gt;()

upstreamPublisher
    .drop(untilOutputFrom: secondPublisher)
    .sink(receiveValue: { print($0)})

upstreamPublisher.send(1)
upstreamPublisher.send(2)

secondPublisher.send(&quot;a&quot;)

upstreamPublisher.send(3)
upstreamPublisher.send(4)

// drop(untilOutputFrom:) 예제 코드
3
4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 두 번째 Publisher에서 값을 받기 전엔 Upstream Publisher에게 받은 값을 모두 무시하므로 결과에는 4, 5만 출력된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적인 작동방식을 조금 알아보자면, 두 번째 Publisher에게 값을 받으면 drop(untilOutputFrom:)은 두 번째 Publisher에 대한 subscribe를 취소한 뒤에 Upstream Publisher의 값을 받기 시작합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Drop&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbbhiY/btrAMuq6pCm/sK1aCkKv8KJV1YGgdhDBB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbbhiY/btrAMuq6pCm/sK1aCkKv8KJV1YGgdhDBB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbbhiY/btrAMuq6pCm/sK1aCkKv8KJV1YGgdhDBB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbbhiY%2FbtrAMuq6pCm%2FsK1aCkKv8KJV1YGgdhDBB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;151&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 Drop입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 특정 개수의 값을 무시한 뒤부터 값을 내려보내는 Publisher라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 바로 Drop을 활용해서 만든 dropFirst(_:)를 살펴볼게요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dropFirst(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beqjKh/btrAJ8JzBha/l7YxwgpdkRi1mDMXYflvkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beqjKh/btrAJ8JzBha/l7YxwgpdkRi1mDMXYflvkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beqjKh/btrAJ8JzBha/l7YxwgpdkRi1mDMXYflvkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeqjKh%2FbtrAJ8JzBha%2Fl7YxwgpdkRi1mDMXYflvkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;261&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dropFirst(_:)의 정의를 보면 특정 개수의 값을 무시한 뒤 부터 값을 내려보낸다고 되어있습니다.&lt;/p&gt;
&lt;p 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_1651160134482&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .dropFirst(3)
    .sink(receiveValue: { print($0) })
    
// dropFirst(_:) 예제 코드
4
5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 3개까지는 무시하고 그 이후의 값들은 Downstream에 전달한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하죠?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DropWhile&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buqOyu/btrAJkwMh1Q/zG79NSL0KRqgRablkJZK80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buqOyu/btrAJkwMh1Q/zG79NSL0KRqgRablkJZK80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buqOyu/btrAJkwMh1Q/zG79NSL0KRqgRablkJZK80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuqOyu%2FbtrAJkwMh1Q%2FzG79NSL0KRqgRablkJZK80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;614&quot; height=&quot;147&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 DropWhile입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 주어진 클로저가 false를 반환할 때까지 Upstream에서 받은 값을 무시하는 Publisher라네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘도 사용해보면 이해가 빠를 거 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;drop(while:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zHzi9/btrAJTeRiMw/xDjElLqN6UKbhwRuH2QBd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zHzi9/btrAJTeRiMw/xDjElLqN6UKbhwRuH2QBd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zHzi9/btrAJTeRiMw/xDjElLqN6UKbhwRuH2QBd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzHzi9%2FbtrAJTeRiMw%2FxDjElLqN6UKbhwRuH2QBd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;289&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DropWhile을 활용해서 만든 Operator는 drop(while:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 매개변수로 받은 클로저가 false를 반환할 때까지 Upstream publisher에서 받은 값을 무시한다고 되어있네요.&lt;/p&gt;
&lt;p 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_1651160494010&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [2, 4, 6, 1, 2, 3, 4, 5].publisher

intPublisher
    .drop { value in
        return value % 2 == 0
    }
    .sink(receiveValue: { print($0) })
    
// drop(while:) 예제 코드
1
2
3
4
5​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 짝수이면 true를, 홀수이면 false를 반환하는 클로저를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 처음으로 받은 홀수 값인 1을 받은 이후부터 값을 Downstream으로 내려보낸 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거도 사용해보니 아주 간단하게 이해할 수 있네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryDropWhile&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SuCJZ/btrAJJXnpsK/u19XbM5suwTAQAXkyd5GLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SuCJZ/btrAJJXnpsK/u19XbM5suwTAQAXkyd5GLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SuCJZ/btrAJJXnpsK/u19XbM5suwTAQAXkyd5GLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSuCJZ%2FbtrAJJXnpsK%2Fu19XbM5suwTAQAXkyd5GLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;150&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 DropWhile에 Try가 붙은 TryDropWhile입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘는 뭐 딱 봐도 DropWhile이랑 동일한 역할인데 에러를 Downstream으로 보낼 수 있다는 차이점만 있어 보이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 정의를 읽어봐도 그렇습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryDrop(while:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnHoTc/btrAMteFb7l/SnVEuu9hCwH7QLVT6bzOT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnHoTc/btrAMteFb7l/SnVEuu9hCwH7QLVT6bzOT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnHoTc/btrAMteFb7l/SnVEuu9hCwH7QLVT6bzOT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnHoTc%2FbtrAMteFb7l%2FSnVEuu9hCwH7QLVT6bzOT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;285&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryDropWhile을 활용해서 만든 Operator는 tryDrop(while:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 알아본 drop(while:)에서 에러만 던질 수 있는 녀석이니 그냥 바로 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1651161159504&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPublisher = [2, 4, 6, 1, 2, -1, 3, 4].publisher
intPublisher
    .tryDrop { value in
        guard value &amp;gt;= 0 else { throw PinguError() }
        return value % 2 == 0
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// tryDrop(while:) 예제 코드
1
2
-1
3
4
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 Upstream에서 받은 값이 음수이면 에러를 던지게 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엥 그런데 -1이 downstream으로 전달되었네요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 결과가 나온 이유는 클로저에서 false가 나올 때까지만 클로저로 값을 처리하고 false가 나온 이후에는 그냥 어떤 값이든 내려보내기 때문입니다.&lt;/p&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가 나오기 전에 에러를 발생시켜야 에러가 전달되는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 값을 수정하면 에러를 발생시킬 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651161344502&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PinguError: Error { }

let intPublisher = [2, 4, 6, -1, 1, 2, 3, 4].publisher
intPublisher
    .tryDrop { value in
        guard value &amp;gt;= 0 else { throw PinguError() }
        return value % 2 == 0
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// tryDrop(while:) 예제 코드
failure(__lldb_expr_20.(unknown context at $10ec823bc).(unknown context at $10ec823c4).(unknown context at $10ec823cc).PinguError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;false가 발생하기 전에 에러를 발생하는 값을 받았기 때문에 에러가 발생되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Concatenate&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1Wnd9/btrAKu6GJXN/74EkZqpmso20TfShuE0lL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1Wnd9/btrAKu6GJXN/74EkZqpmso20TfShuE0lL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1Wnd9/btrAKu6GJXN/74EkZqpmso20TfShuE0lL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1Wnd9%2FbtrAKu6GJXN%2F74EkZqpmso20TfShuE0lL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;199&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Drop 시리즈가 아닌 Concatenate Publisher를 알아보겠습니다. Concatenate가 무슨 뜻인고 하니 연결하다는 뜻이 있네요. 정의를 보면 어떤 Publisher의 모든 값을 다른 Publisher의 값보다 먼저 내보내는 Publisher라고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 보면 이해가 잘 안 되는데요, 이걸 활용해서 만든 Operator를 보면서 이해해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;append(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DSwNj/btrAJCj81V5/XrcOtqVktGdELvc5owCy30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DSwNj/btrAJCj81V5/XrcOtqVktGdELvc5owCy30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DSwNj/btrAJCj81V5/XrcOtqVktGdELvc5owCy30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDSwNj%2FbtrAJCj81V5%2FXrcOtqVktGdELvc5owCy30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;265&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Concatenate Publisher를 활용해서 만든 append(_:)를 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값들에 매개변수로 받은 값들을 추가해주는 역할을 한다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Array에 append 메서드와 비슷한 역할을 하는 거 같네요.&lt;/p&gt;
&lt;p 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_1651161775247&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4].publisher

intPublisher
    .append(5, 6)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
    
// append(_:) 예제 코드
1
2
3
4
5
6
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 Upstream에서 값을 모두 받은 뒤에 매개변수로 받은 값을 내보내고 나서야 finished를 내려보내는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;append(_:)의 매개변수에는 Sequence 타입도 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651162271305&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let stringPublisher = [&quot;My&quot;, &quot;Name&quot;].publisher

stringPublisher
    .append([&quot;Is&quot;, &quot;Pingu&quot;])
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

// append(_:) 예제 코드
My
Name
Is
Pingu
finished&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;그리고 매개변수로 Publisher도 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651162370338&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = [&quot;My&quot;, &quot;Name&quot;].publisher
let secondPublisher = [&quot;Is&quot;, &quot;Pingu&quot;].publisher

firstPublisher
    .append(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// append(_:) using Publisher 예제 코드
My
Name
Is
Pingu
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;prepend(_:)&lt;/h3&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;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OADZF/btrAJTMDZ1p/jJ8LtVrjL3YkUAmuDt19QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OADZF/btrAJTMDZ1p/jJ8LtVrjL3YkUAmuDt19QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OADZF/btrAJTMDZ1p/jJ8LtVrjL3YkUAmuDt19QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOADZF%2FbtrAJTMDZ1p%2FjJ8LtVrjL3YkUAmuDt19QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;274&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 녀석은 prepend(_:)입니다. 얘도 append(_:)와 동일하게 Concatenate Publisher를 활용해서 만든 Operator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 아까 append(_:)는 Upstream Publisher의 값을 먼저 내려보냈는데, prepend(_:)는 매개변수로 받은 값들을 먼저 내려보낸다고 합니다.&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;그리고 append(_:)와 마찬가지로 prepend(_:)도 매개변수로 값들과 Sequence, Publisher를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 3가지를 다 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1651162668817&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4].publisher

intPublisher
    .prepend(5, 6)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// prepend(_:) using values 예제 코드
5
6
1
2
3
4
finished&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1651162702766&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4].publisher

intPublisher
    .prepend([5, 6])
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

// prepend(_:) using Sequence 예제 코드
5
6
1
2
3
4
finished&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1651162744165&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPublisher = [1, 2, 3, 4].publisher
let secondPublisher = [5, 6].publisher

firstPublisher
    .prepend(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

// prepend(_:) using Publisher 예제 코드
5
6
1
2
3
4
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 모두 사용해보니 정말 간단하다는 것을 알 수 있네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PrefixWhile&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnEOZQ/btrAMtsLAPI/0eMt6XRpdbutYeIBMGom5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnEOZQ/btrAMtsLAPI/0eMt6XRpdbutYeIBMGom5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnEOZQ/btrAMtsLAPI/0eMt6XRpdbutYeIBMGom5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnEOZQ%2FbtrAMtsLAPI%2F0eMt6XRpdbutYeIBMGom5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;159&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 PrefixWhile입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 prefix라는 녀석들은 앞쪽의 몇 개만 처리하거나 하는 역할을 하니 비슷한 역할을 할 거 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보니 predicate 클로저를 만족할 때까지 값을 계속 내려보내는 Publisher라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 얘를 활용해서 만든 Operator들을 보며 이해해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;prefix(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yVwFB/btrAOAq85y9/BA9QkWzazrKhT0skl6Z7UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yVwFB/btrAOAq85y9/BA9QkWzazrKhT0skl6Z7UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yVwFB/btrAOAq85y9/BA9QkWzazrKhT0skl6Z7UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyVwFB%2FbtrAOAq85y9%2FBA9QkWzazrKhT0skl6Z7UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;241&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrefixWhile Publisher를 활용해서 만든 Operator는 아니지만 비슷한 역할을 하는 Operator인 prefix(_:)부터 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 매개변수로 받은 개수만큼만 Downstream으로 내려보내는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p 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_1651202793128&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .prefix(3)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// prefix(_:) 예제 코드
1
2
3
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 3개까지만 내려보내라고 했더니 결과도 3개만 내려보낸 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;prefix(while:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN8EMD/btrALUYoO9R/mY1nmy6aQJyqH4AFRBrkh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN8EMD/btrALUYoO9R/mY1nmy6aQJyqH4AFRBrkh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN8EMD/btrALUYoO9R/mY1nmy6aQJyqH4AFRBrkh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN8EMD%2FbtrALUYoO9R%2FmY1nmy6aQJyqH4AFRBrkh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;278&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 PrefixWhile Publisher를 활용해서 만든 prefix(while:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 매개변수로 받은 클로저가 false를 반환할 때까지 값을 내려보내는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p 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_1651203036031&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 3, 2, 1].publisher

intPublisher
    .prefix { value in
        return value &amp;lt;= 3
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// prefix(while:) 예제 코드
1
2
3
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 클로저에서 false가 나올 때 까지는 값을 내려보내다가 false가 나오는 순간에 finished를 보낸 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryPrefixWhile&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s6VOX/btrAONKIIJh/mjRG2d8MZwC1OAzNbnXVI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s6VOX/btrAONKIIJh/mjRG2d8MZwC1OAzNbnXVI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s6VOX/btrAONKIIJh/mjRG2d8MZwC1OAzNbnXVI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs6VOX%2FbtrAONKIIJh%2FmjRG2d8MZwC1OAzNbnXVI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;151&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 알아볼 녀석은 PrefixWhile의 Try 버전입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrefixWhile과 동일한 역할을 하면서 에러를 던질 수 있다는 차이점만 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryPrefix(while:)&lt;/h3&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;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KY9bS/btrAOOXamDk/dhl0V4EksweYSgPM6wR2g0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KY9bS/btrAOOXamDk/dhl0V4EksweYSgPM6wR2g0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KY9bS/btrAOOXamDk/dhl0V4EksweYSgPM6wR2g0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKY9bS%2FbtrAOOXamDk%2Fdhl0V4EksweYSgPM6wR2g0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;286&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryPrefixWhile Publisher를 활용해서 만든 Operator는 tryPrefix(while:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 얘도 prefix(while:)과 동일한 역할을 하면서 에러만 던질 수 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 사용해보고 넘어가겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651203403368&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PinguError : Error { }

let intPublisher = [1, 2, 3, 4, 3, 2, 1].publisher

intPublisher
    .tryPrefix { value in
        guard value &amp;lt;= 3 else { throw PinguError() }
        return value &amp;lt;= 3
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
          
// tryPrefix(while:) 예제 코드
1
2
3
failure(__lldb_expr_11.(unknown context at $104ee169c).(unknown context at $104ee16a4).(unknown context at $104ee16ac).PinguError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와 동일하게 3보다 작으면 true를 반환하는 클로저를 매개변수로 사용했습니다. 그리고 이번에는 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;결과를 보면 예상대로 4를 받았을 때 에러가 던져진 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PrefixUntilOutput&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdlc4q/btrANUpQE6e/gadNTP9aWIZivKpR18RbG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdlc4q/btrANUpQE6e/gadNTP9aWIZivKpR18RbG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdlc4q/btrANUpQE6e/gadNTP9aWIZivKpR18RbG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdlc4q%2FbtrANUpQE6e%2FgadNTP9aWIZivKpR18RbG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;171&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 마지막 Publisher는 PrefixUntilOutput입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream이 아닌 다른 Publisher가 값을 내려보낼 때까지만 값을 내려보내는 Publisher라고 합니다.&lt;/p&gt;
&lt;p 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_1651203632370&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct PrefixUntilOutput&amp;lt;Upstream, Other&amp;gt; : Publisher where Upstream : Publisher, Other : Publisher {
    public typealias Output = Upstream.Output

    public typealias Failure = Upstream.Failure

    public let upstream: Upstream

    public let other: Other

    public init(upstream: Upstream, other: Other)

    public func receive&amp;lt;S&amp;gt;(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrefixUntilOutput에는 upstream과 other라는 Publisher가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의에서 말했듯 other라는 Publisher가 값을 내보낼 때까지만 Upstream의 값을 내려보내겠다는 뜻이겠네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;prefix(untilOutputFrom:)&lt;/h3&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;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnBMyW/btrAJ0x2Kmi/zjPgQusbXks4qL8T0U3sK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnBMyW/btrAJ0x2Kmi/zjPgQusbXks4qL8T0U3sK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnBMyW/btrAJ0x2Kmi/zjPgQusbXks4qL8T0U3sK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnBMyW%2FbtrAJ0x2Kmi%2FzjPgQusbXks4qL8T0U3sK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;258&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrefixUntilOutput Publisher를 활용해서 만든 Operator는 prefix(untilOutputFrom:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 매개변수로 받은 Publisher가 값을 내보낼 때까지만 값을 내려보낸다고 합니다.&lt;/p&gt;
&lt;p 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_1651204143978&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let upstreamPublisher = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let otherPublisher = PassthroughSubject&amp;lt;String, Never&amp;gt;()

upstreamPublisher
    .prefix(untilOutputFrom: otherPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

upstreamPublisher.send(1)
upstreamPublisher.send(2)

otherPublisher.send(&quot;Pingu&quot;)

upstreamPublisher.send(3)
upstreamPublisher.send(4)

// prefix(untilOutputFrom:) 예제 코드
1
2
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 otherPublisher가 값을 내보낼 때까지만 upstreamPublisher의 값을 내려보내는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;otherPublisher가 &quot;Pingu&quot;를 내려보낸 이후에는 upstreamPublisher가 finished 돼서 이후에 보낸 3, 4는 출력이 안 되는 것을 볼 수 있어요.&lt;/p&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의 Publisher와 Operator 중에서 Applying Sequence Operations to Elements로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&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://icksw.tistory.com/283&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Selecting Specific Elements로 분류된 녀석들을 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/ApplyingSequenceOperationsToElements.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>CONCATENATE</category>
      <category>DROP</category>
      <category>DropUntilOutput</category>
      <category>DropWhile</category>
      <category>PrefixUntilOutput</category>
      <category>PrefixWhile</category>
      <category>Swift</category>
      <category>TryDropWhile</category>
      <category>TryPrefixWhile</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/282</guid>
      <comments>https://icksw.tistory.com/282#entry282comment</comments>
      <pubDate>Fri, 29 Apr 2022 12:55:20 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Applying Matching Criteria to Elements - Operator 공부 5</title>
      <link>https://icksw.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Applying Mathematical Operations on Elements로 분류된 Operator들을 알아봤었습니다. Upstream에서 받은 값은 간단한 수학 연산으로 처리해서 만든 새로운 값을 Downstream으로 보내는 역할을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이어서 Applying Matching Criteria to Elements로 분류된 Operator에 대해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Applying Matching Criteria to Elements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공부할 녀석들을 분류해둔 이름을 보면 값들이 어떤 기준에 맞는지 확인하는 역할을 할 거 같네요. 그럼 여기에 분류된 Publisher에는 어떤 것들이 있는지부터 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Contains&lt;/li&gt;
&lt;li&gt;ContainsWhere&lt;/li&gt;
&lt;li&gt;TryContainsWhere&lt;/li&gt;
&lt;li&gt;AllStatisfy&lt;/li&gt;
&lt;li&gt;TryAllSatisfy&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 Publisher들을 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;contains(_:)&lt;/li&gt;
&lt;li&gt;contains(where:)&lt;/li&gt;
&lt;li&gt;tryContains(where:)&lt;/li&gt;
&lt;li&gt;allSatisfy(_:)&lt;/li&gt;
&lt;li&gt;tryAllSatisfy(_:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 바로 어떤 역할들을 하는 것들인지 하나씩 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Contains&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfv3DN/btrzItBHPYo/kPBKdMRI0l7kiF6iHQBFCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfv3DN/btrzItBHPYo/kPBKdMRI0l7kiF6iHQBFCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfv3DN/btrzItBHPYo/kPBKdMRI0l7kiF6iHQBFCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfv3DN%2FbtrzItBHPYo%2FkPBKdMRI0l7kiF6iHQBFCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;266&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 알아볼 Publisher는 Contains입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 특정한 값을 받으면 Downstream으로 Bool 값을 내려보내는 Publisher라고 하네요. 이걸 활용해서 만든 &amp;nbsp;Operator를 봐야 어떤 Bool 값을 내려보내는지 알 수 있겠네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;contains(_:)&lt;/h3&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;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ryfb5/btrzJ3IY57J/KThUIH9JmdEf9GOAkrkRdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ryfb5/btrzJ3IY57J/KThUIH9JmdEf9GOAkrkRdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ryfb5/btrzJ3IY57J/KThUIH9JmdEf9GOAkrkRdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fryfb5%2FbtrzJ3IY57J%2FKThUIH9JmdEf9GOAkrkRdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;273&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Contains Publisher를 활용해서 만든 Operator는 contains(_:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 매개변수로 받은 값과 동일한 값을 Upstream에서 받으면 Bool 값을 내려보낸다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 같은 값인지 확인을 해야 하니까 Output은 Equatable 프로토콜을 준수해야 할 거 같네요.&lt;/p&gt;
&lt;p 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_1650291304509&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .contains(3)
    .sink(receiveValue: { print($0) })
    
// contains(_:) 예제 코드
true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3이랑 같은 값이 있기 때문에 Bool 값으로 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;pre id=&quot;code_1650291649219&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [3, 3, 3].publisher

intPublisher
    .contains(3)
    .sink(receiveValue: { print($0) })
    
// contains(_:) 예제 코드 결과
true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 contains(_:)에 3을 주고 [3, 3, 3]의 값을 publish 하면 결과는 어떻게 나올까요?&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;즉 일치하는 값을 받는 순간 그냥 finish 되므로 위의 예의 경우 &lt;span&gt;publisher는&amp;nbsp;&lt;/span&gt;첫 번째 3만 전달받고 바로 finish 됩니다.&lt;/p&gt;
&lt;p 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_1650291766505&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 1, 1].publisher

intPublisher
    .contains(3)
    .sink(receiveValue: { print($0) })
    
// contains(_:) 예제 코드 결과
false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없으면 위와 같이 false를 내려보내는 것을 볼 수 있습니다.&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;ContainsWhere&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 ContainsWhere라는 Publisher를 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D3mdy/btrzBtvfSlq/uw6xKSlCfMDYXoTz3mPQoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D3mdy/btrzBtvfSlq/uw6xKSlCfMDYXoTz3mPQoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D3mdy/btrzBtvfSlq/uw6xKSlCfMDYXoTz3mPQoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD3mdy%2FbtrzBtvfSlq%2Fuw6xKSlCfMDYXoTz3mPQoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;240&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값이 클로저의 로직을 만족시키면 Bool값을 내려보낸다고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 범위를 지정해서 처리하거나 하는 것이 가능해 보이네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;contains(where:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/liwBy/btrzNA6G9na/cgUzodNpQqAmUotASu1x0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/liwBy/btrzNA6G9na/cgUzodNpQqAmUotASu1x0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/liwBy/btrzNA6G9na/cgUzodNpQqAmUotASu1x0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FliwBy%2FbtrzNA6G9na%2FcgUzodNpQqAmUotASu1x0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;277&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ContainsWhere Publisher를 활용해서 만든 Operator는 contains(where:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream에서 받은 값이 매개변수로 전달한 클로저를 만족시키면 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;바로 사용해보면 아래와 같이 사용할 수 있겠네요.&lt;/p&gt;
&lt;pre id=&quot;code_1650291605768&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .contains(where: { $0 % 2 == 0 })
    .sink(receiveValue: { print($0) })

// contains(where:) 예제 코드 결과
true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하죠? 받은 값 중에 클로저를 만족시키면 true를 하나도 만족시키지 못하면 False를 downstream으로 보내는 Operator입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryContainsWhere&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYHNK/btrzLHyqaBa/8LKc6a9LhS8YKiMCjJDow1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYHNK/btrzLHyqaBa/8LKc6a9LhS8YKiMCjJDow1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYHNK/btrzLHyqaBa/8LKc6a9LhS8YKiMCjJDow1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYHNK%2FbtrzLHyqaBa%2F8LKc6a9LhS8YKiMCjJDow1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;238&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 TryContainsWhere은 방금 알아본 ContainsWhere에서 에러를 던질 수 있다는 차이만 있는 Publisher입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryContains(where:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b42OIo/btrzKRuMhnc/kLWrHPbueir9nVIbC6DxSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b42OIo/btrzKRuMhnc/kLWrHPbueir9nVIbC6DxSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b42OIo/btrzKRuMhnc/kLWrHPbueir9nVIbC6DxSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb42OIo%2FbtrzKRuMhnc%2FkLWrHPbueir9nVIbC6DxSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;298&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 TryContainsWhere를 활용해서 만든 tryContains(where:) Operator도 아까 알아본 contains(where:)에서 에러만 던진다는 차이점만 존재합니다.&lt;/p&gt;
&lt;p 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_1650292184525&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct MoreThenTenError: Error { }

let intPublisher = [1, 3, 5, 7, 9, 11].publisher

intPublisher
    .tryContains { value in
        guard value &amp;lt; 10 else {
            throw MoreThenTenError()
        }
        return value &amp;lt; 10
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) })
          
// tryContains(where:) 예제 코드 결과
value: true
completion: finished&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;10 이상의 값을 받으면 에러를 발생하도록 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엥? 근데 분명 11이라는 값도 내려보낼 텐데 에러가 발생하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 contains(_:), contains(where:), tryContains(where:) 모두 만족하는 값을 받으면 바로 true를 내려보내고 Publisher는 finish 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 방금 예제에서는 1을 받자마자 true를 내려보내고 finish 된 것이죠.&lt;/p&gt;
&lt;p 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_1650292474117&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct MoreThenTenError: Error { }

let intPublisher = [11, 9, 7, 5, 3, 1].publisher

intPublisher
    .tryContains { value in
        guard value &amp;lt; 10 else {
            throw MoreThenTenError()
        }
        return value &amp;lt; 10
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) })

// tryContains(where:) 예제 코드 결과
completion: failure(__lldb_expr_21.(unknown context at $10f2b109c).(unknown context at $10f2b10f0).(unknown context at $10f2b10f8).MoreThenTenError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 10보다 큰 11을 먼저 내려보냈더니 에러가 발생해서 failure가 발생하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AllSatisfy&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3WlED/btrzDNNH489/glbqeCv4SBWV5MtLIcpV6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3WlED/btrzDNNH489/glbqeCv4SBWV5MtLIcpV6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3WlED/btrzDNNH489/glbqeCv4SBWV5MtLIcpV6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3WlED%2FbtrzDNNH489%2FglbqeCv4SBWV5MtLIcpV6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;228&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 Publisher는 AllSatisfy입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만 봐도 Upstream에서 받은 모든 값이 원하는 값과 일치하는지를 볼 거 같이 생겼네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;allSatisfy(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc4C6g/btrzBspBBhW/Fto5kBCWC1RCZarBzcERCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc4C6g/btrzBspBBhW/Fto5kBCWC1RCZarBzcERCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc4C6g/btrzBspBBhW/Fto5kBCWC1RCZarBzcERCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc4C6g%2FbtrzBspBBhW%2FFto5kBCWC1RCZarBzcERCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;280&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;allSatisfy(_:)는 Upstream에서 받은 모든 값이 클로저를 만족시키면 true를, 하나라도 만족시키지 못한다면 false를 downstream에 내려보냅니다.&lt;/p&gt;
&lt;p 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_1650292831028&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [2, 2, 2, 2].publisher

intPublisher
    .allSatisfy { $0 == 2 }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;)})
          
// allSatisfy(_:) 예제 코드 결과
value: true
completion: finished&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;지금까지 알아본 contains라는 이름을 가진 Operator들은 모두 만족하는 값을 하나라도 받으면 finish 했지만, 이번에는 그 반대로 만족시키지 못하는 값을 하나라도 받으면 finish 됩니다. 물론 모든 값이 조건을 만족하는지 확인하려면 모든 값을 확인해야 하니 당연한 말입니다. &lt;/p&gt;
&lt;p 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_1650293233091&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [2, 2, 3, 2].publisher

intPublisher
    .allSatisfy {
        print(&quot;Upstream에게 받은 값: \($0)&quot;)
        return $0 == 2
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;)})

// allSatisfy(_:) 예제 코드 결과
Upstream에게 받은 값: 2
Upstream에게 받은 값: 2
Upstream에게 받은 값: 3
value: false
completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 만족시키지 못하는 값을 받으면 바로 에러를 발생시키고 finish 되는 것을 볼 수 있어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryAllSatisfy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이번 글의 마지막 Publisher인 TryAllSatisfy를 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcOpSE/btrzNATaqmc/GuB1gsUTKTag56dXR1wcM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcOpSE/btrzNATaqmc/GuB1gsUTKTag56dXR1wcM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcOpSE/btrzNATaqmc/GuB1gsUTKTag56dXR1wcM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcOpSE%2FbtrzNATaqmc%2FGuB1gsUTKTag56dXR1wcM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;230&quot; data-origin-width=&quot;960&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;뭐 역시나 AllSatisfy랑 같으면서 에러만 던질 수 있는 녀석입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryAllSatisfy(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE6qvm/btrzJ3a9Azi/LbkKkqW6DzwhjGF7OfCRb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE6qvm/btrzJ3a9Azi/LbkKkqW6DzwhjGF7OfCRb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE6qvm/btrzJ3a9Azi/LbkKkqW6DzwhjGF7OfCRb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE6qvm%2FbtrzJ3a9Azi%2FLbkKkqW6DzwhjGF7OfCRb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;309&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tryAllSatisfy(_:)도 allSatisfy(_:)에서 에러만 던질 수 있는 차이밖에 없네요.&lt;/p&gt;
&lt;p 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_1650293458565&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct MoreThenTenError: Error { }

let intPublisher = [7, 9, 11, 13].publisher

intPublisher
    .tryAllSatisfy { value in
        print(&quot;Upstream에게 받은 값: \(value)&quot;)
        guard value &amp;lt; 10 else {
            throw MoreThenTenError()
        }
        return value &amp;lt; 10
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) })
          
// tryAllSatisfy(_:) 예제 코드 결과
Upstream에게 받은 값: 7
Upstream에게 받은 값: 9
Upstream에게 받은 값: 11
completion: failure(__lldb_expr_31.(unknown context at $10d2e86dc).(unknown context at $10d2e875c).(unknown context at $10d2e8764).MoreThenTenError())&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;조건에 맞지 않는 값을 받자마자 에러를 발생시키고 Publisher는 failure로 처리됩니다.&lt;/p&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의 Publisher와 Operator 중에서 Applying Matching Criteria to Elements로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&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://icksw.tistory.com/282&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Applying Sequence Operations to Elements로 분류된 녀석들을 알아볼 예정인데... 엄청 많네요. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/ApplyingMatchingCriteriaToElements.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>allSatisfy</category>
      <category>Combine</category>
      <category>contains</category>
      <category>ContainsWhere</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>Swift</category>
      <category>tryAllSatisfy</category>
      <category>TryContainsWhere</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/281</guid>
      <comments>https://icksw.tistory.com/281#entry281comment</comments>
      <pubDate>Mon, 18 Apr 2022 23:57:29 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Applying Mathematical Operations on Elements - Operator 공부 4</title>
      <link>https://icksw.tistory.com/280</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/279&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Reducing Elements 역할을 하는 Operator들을 알아봤었습니다. Upstream에서 받은 값들을 모아서 한 번에 Downastream으로 내려보내는 역할을 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이어서 Applying&amp;nbsp;Mathematical&amp;nbsp;Operations&amp;nbsp;on&amp;nbsp;Elements로 분류된 Operator에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Applying&amp;nbsp;Mathematical&amp;nbsp;Operations&amp;nbsp;on&amp;nbsp;Elements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 보면 Elements에 수학 연산을 적용한다라고 되어있는데요, Publishers에 이걸로 분류된 것에는 어떤 것들이 있는지부터 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Count&lt;/li&gt;
&lt;li&gt;Comparison&lt;/li&gt;
&lt;li&gt;TryComparison&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine/publishers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에는 위와 같이 3개가 Applying&amp;nbsp;Mathematical&amp;nbsp;Operations&amp;nbsp;on&amp;nbsp;Elements로 분류되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만 봐서는 뭔가를 세고, 비교하는 역할을 할 거 같은데요, 이걸 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;count()&lt;/li&gt;
&lt;li&gt;max()&lt;/li&gt;
&lt;li&gt;max(by:)&lt;/li&gt;
&lt;li&gt;tryMax(by:)&lt;/li&gt;
&lt;li&gt;min()&lt;/li&gt;
&lt;li&gt;min(by:)&lt;/li&gt;
&lt;li&gt;tryMin(by:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine/publisher&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에는 위와 같이 7개가 Applying&amp;nbsp;Mathematical&amp;nbsp;Operations&amp;nbsp;on&amp;nbsp;Elements Publishers로 만든 Operator로 분류되어있습니다. 이름들이 직관적이라 대충 봐도 뭐하는 녀석들인지 느껴지네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 알아볼게요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Count&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4XCeG/btrzx83N04r/7K50FfjXSD2PbtRalvPCb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4XCeG/btrzx83N04r/7K50FfjXSD2PbtRalvPCb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4XCeG/btrzx83N04r/7K50FfjXSD2PbtRalvPCb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4XCeG%2Fbtrzx83N04r%2F7K50FfjXSD2PbtRalvPCb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;228&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 먼저 Count라는 Publisher를 알아볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값의 개수를 Downstream으로 보내는 역할을 하는거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하네요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Comparison&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jb5my/btrzuNgmlyj/b6myMg3OxKL9xJ3AH4TVv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jb5my/btrzuNgmlyj/b6myMg3OxKL9xJ3AH4TVv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jb5my/btrzuNgmlyj/b6myMg3OxKL9xJ3AH4TVv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJb5my%2FbtrzuNgmlyj%2Fb6myMg3OxKL9xJ3AH4TVv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;226&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Comparison이라는 Publisher부터 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 새로운 아이템이 이전의 것들보다 오름차순? 인 경우에만 Downstream에 내보낸다고 되어있는데.. 오름차순? 이 이상해서 구현부를 한 번 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1650043027185&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct Comparison&amp;lt;Upstream&amp;gt; : Publisher where Upstream : Publisher {
	public typealias Output = Upstream.Output

    public typealias Failure = Upstream.Failure

    public let upstream: Upstream

    public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) -&amp;gt; Bool

    public init(upstream: Upstream, areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -&amp;gt; Bool)

    public func receive&amp;lt;S&amp;gt;(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 areInIncreasingOrder라는 게 있는데, 여기서 오름차순이라고 표현한 건 이 클로저에서 true를 반환하는 거라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 일반적인 오름차순이 아니고.. 어떤 것이던 저 클로저에서 true를 반환하면 그걸 Downstream에 내려보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 참 애매하네요 ㅋㅋㅋ&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryComparison&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s4rf2/btrzyX8PS2S/HY8AL7qSOvUky18oYJcr90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s4rf2/btrzyX8PS2S/HY8AL7qSOvUky18oYJcr90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s4rf2/btrzyX8PS2S/HY8AL7qSOvUky18oYJcr90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs4rf2%2FbtrzyX8PS2S%2FHY8AL7qSOvUky18oYJcr90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;252&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이번엔 Comparison에 Try가 붙은 TryComparison을 알아볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Try가 붙었으니 Comparison과 똑같으면서 에러를 내려보낼 수 있다는 차이만 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 이 세 개의 Publisher를 활용해서 만들어진 Operator를 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;count()&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mnxws/btrzvS10A7u/cIYp2CbVl0kTQc0ShWCJV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnxws/btrzvS10A7u/cIYp2CbVl0kTQc0ShWCJV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnxws/btrzvS10A7u/cIYp2CbVl0kTQc0ShWCJV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmnxws%2FbtrzvS10A7u%2FcIYp2CbVl0kTQc0ShWCJV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;234&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 알아본 Count Publisher를 활용해서 만들어진 Operator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값의 개수를 세어주는 녀석이라고 합니다.&lt;/p&gt;
&lt;p 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_1650041360181&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .count()
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650041375546&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// count() 예제 코드
5&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;위 코드를 보면 intPublisher가 내보내는 값은 총 5개니까 출력도 5로 된 것을 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝입니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;max()&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAMsw/btrzwQbMpVS/qoBB0a2h9dAZKE9VnZrCX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAMsw/btrzwQbMpVS/qoBB0a2h9dAZKE9VnZrCX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAMsw/btrzwQbMpVS/qoBB0a2h9dAZKE9VnZrCX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAMsw%2FbtrzwQbMpVS%2FqoBB0a2h9dAZKE9VnZrCX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;299&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 알아본 Comparison을 활용해서 만들어진 Operator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream이 finish 됐을 때 그때까지 받은 값 중에 가장 큰 값을 Downstream에 보내는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 말이지만 Output은 Comparable 프로토콜을 준수해야 합니다.&lt;/p&gt;
&lt;p 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_1650043531607&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [5, 4, 10, 2, 1].publisher

intPublisher
    .max()
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 당연하게도 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650043547909&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// max() 예제 코드
10&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;max(by:)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbezNU/btrzw992kNT/oku1sGvRSZWXlgVmnJTPP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbezNU/btrzw992kNT/oku1sGvRSZWXlgVmnJTPP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbezNU/btrzw992kNT/oku1sGvRSZWXlgVmnJTPP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbezNU%2Fbtrzw992kNT%2Foku1sGvRSZWXlgVmnJTPP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;274&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 본 max()와 다르게 Comparison의 areInIncreasingOrder, 즉 비교 로직을 직접 구현할 수 있는 operator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 Output이 Comparable 프로토콜을 준수하지 않을 때 사용하면 좋습니다.&lt;/p&gt;
&lt;p 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_1650043728016&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Person {
    let name: String
    let age: Int
}

let personPublisher = [
    Person(name: &quot;Pingu&quot;, age: 28),
    Person(name: &quot;Pinga&quot;, age: 23),
    Person(name: &quot;Roby&quot;, age: 5)
].publisher

personPublisher
    .max { $0.age &amp;lt; $1.age }
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650043745148&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// max(by:) 예제 코드
Person(name: &quot;Pingu&quot;, age: 28)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 Comparable을 준수하지 않는 Person이라는 구조체를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 age가 가장 큰 값을 내보내도록 로직을 구현해줬어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결과는 위와 같이 age값이 가장 큰 Person 객체가 나오는 걸 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;tryMax(by:)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qkafb/btrzwPKIpdP/p9LR8LFuulUEtmZrXaK6H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qkafb/btrzwPKIpdP/p9LR8LFuulUEtmZrXaK6H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qkafb/btrzwPKIpdP/p9LR8LFuulUEtmZrXaK6H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqkafb%2FbtrzwPKIpdP%2Fp9LR8LFuulUEtmZrXaK6H0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;282&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Try 시리즈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 사용해본 max에 에러도 내려보낼 수 있는 녀석입니다.&lt;/p&gt;
&lt;p 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_1650043993728&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct NagativeNumberError: Error { }

let intPublisher = [5, 4, 10, -2, 1].publisher

intPublisher
    .tryMax { first, second in
        if second &amp;lt; 0 {
            throw NagativeNumberError()
        }
        return first &amp;lt; second
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 사용할 수 있고 결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650044020845&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tryMax(by:) 예제 코드
completion: failure(__lldb_expr_14.(unknown context at $105e0c39c).(unknown context at $105e0c3a4).(unknown context at $105e0c3ac).NagativeNumberError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 기존 값과 비교할 새로운 값이 음수면 에러를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 기존 값에 음수인지 비교하는 로직으로 구현하면 어떻게 될까요?&lt;/p&gt;
&lt;pre id=&quot;code_1650044093097&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct NagativeNumberError: Error { }

let intPublisher = [5, 4, 10, -2, 1].publisher

intPublisher
    .tryMax { first, second in
//      if second &amp;lt; 0 {
        if first &amp;lt; 0 {
            throw NagativeNumberError()
        }
        return first &amp;lt; second
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 에러가 발생하지 않고 아래와 같은 결과를 보여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650044131033&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tryMax(by:) 예제 코드
value: 10
completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 값은 계속해서 양수니까 에러가 발생하지 않습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;min()&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xqTrd/btrzvx5mr7r/2FfPIqz5ACyaU8TCSrfSe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xqTrd/btrzvx5mr7r/2FfPIqz5ACyaU8TCSrfSe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xqTrd/btrzvx5mr7r/2FfPIqz5ACyaU8TCSrfSe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxqTrd%2Fbtrzvx5mr7r%2F2FfPIqz5ACyaU8TCSrfSe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;306&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 min 시리즈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘는 max와 반대로 최솟값을 Downstream으로 내려보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;min()도 Output이 Comparable을 준수해야지 사용할 수 있습니다.&lt;/p&gt;
&lt;p 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_1650044310941&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .min()
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650044325226&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// min() 예제 코드
1&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;min(by:)&lt;/h2&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;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mUWYG/btrzxa18lrQ/bKknKbvyI1E5pOcMbADaK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mUWYG/btrzxa18lrQ/bKknKbvyI1E5pOcMbADaK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mUWYG/btrzxa18lrQ/bKknKbvyI1E5pOcMbADaK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmUWYG%2Fbtrzxa18lrQ%2FbKknKbvyI1E5pOcMbADaK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;274&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 max(by:)와 마찬가지로 최소값을 결정하는 로직을 직접 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;max(by:)와 마찬가지로 Output이 Comparable을 준수하지 않을 때 유용합니다.&lt;/p&gt;
&lt;p 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_1650044440764&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Person {
    let name: String
    let age: Int
}

let personPublisher = [
    Person(name: &quot;Pingu&quot;, age: 28),
    Person(name: &quot;Pinga&quot;, age: 23),
    Person(name: &quot;Roby&quot;, age: 5)
].publisher

personPublisher
    .min { $0.age &amp;lt; $1.age }
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650044810671&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// min(by:) 예제 코드
Person(name: &quot;Roby&quot;, age: 5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 Comparable을 준수하지 않는 Person이라는 구조체를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 age가 가장 작은 값을 내보내도록 로직을 구현했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 위와 같이 age값이 가장 작은 Person 객체가 나온 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 로직을 보면 &lt;b&gt;&quot;더 큰 값을 내보내는 로직 아닌가용?&quot;&lt;/b&gt;이라고 의문이 들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 저도 의문이 들어서 좀 확인해봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650045207188&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Person {
    let name: String
    let age: Int
}

let personPublisher = [
    Person(name: &quot;Pingu&quot;, age: 28),
    Person(name: &quot;Pinga&quot;, age: 23),
    Person(name: &quot;Roby&quot;, age: 5)
].publisher

personPublisher
    .min {
        print(&quot;first: \($0), second: \($1)&quot;)
        return $0.age &amp;lt; $1.age
    }
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1650045234822&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// min(by:) 예제 코드
first: Person(name: &quot;Pinga&quot;, age: 23), second: Person(name: &quot;Pingu&quot;, age: 28)
first: Person(name: &quot;Roby&quot;, age: 5), second: Person(name: &quot;Pinga&quot;, age: 23)
Person(name: &quot;Roby&quot;, age: 5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 비교를 할 때 max와 다르게 비교하는 두 개의 값의 위치가 반대입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 첫 번째 비교에서 first는 Pingu이고 second는 Pinga여야 정상적인 순서인데, 이게 바뀌어있는 걸 볼 수 있어요.&lt;/p&gt;
&lt;p 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;h2 data-ke-size=&quot;size26&quot;&gt;tryMin(by:)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXKWaO/btrzvx5m912/f2JEv84pud5KtlIFyXgvI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXKWaO/btrzvx5m912/f2JEv84pud5KtlIFyXgvI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXKWaO/btrzvx5m912/f2JEv84pud5KtlIFyXgvI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXKWaO%2Fbtrzvx5m912%2Ff2JEv84pud5KtlIFyXgvI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;285&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 min(by:)이랑 똑같은데 에러를 내려보낼 수 있다는 차이가 있습니다.&lt;/p&gt;
&lt;p 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_1650045531836&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct NameIsPinguError: Error { }

struct Person {
    let name: String
    let age: Int
}

let personPublisher = [
    Person(name: &quot;Pinga&quot;, age: 23),
    Person(name: &quot;Pingu&quot;, age: 28),
    Person(name: &quot;Roby&quot;, age: 5)
].publisher

personPublisher
    .tryMin { first, second in
        if first.name == &quot;Pingu&quot; {
            throw NameIsPinguError()
        }
        return first.age &amp;lt; second.age
    }
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;completion: \($0)&quot;) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1650045560103&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tryMin(by:) 예제 코드
completion: failure(__lldb_expr_39.(unknown context at $10662e7ec).(unknown context at $10662e89c).(unknown context at $10662e8a4).NameIsPinguError())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Combine의 Operator 중 Applying&amp;nbsp;Mathematical&amp;nbsp;Operations&amp;nbsp;on&amp;nbsp;Elements로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 알아본 것들에 비해 비교적 간단하다는 느낌을 받았네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icksw.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Applying Matching Criteria to Elements로 분류된 Operator에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/ApplyingMathematicalOperationsOnElements.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>comparison</category>
      <category>count</category>
      <category>IOS</category>
      <category>MAX</category>
      <category>Min</category>
      <category>Swift</category>
      <category>TryComparison</category>
      <category>tryMax</category>
      <category>tryMin</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/280</guid>
      <comments>https://icksw.tistory.com/280#entry280comment</comments>
      <pubDate>Sat, 16 Apr 2022 03:04:03 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Reducing Elements Operator - Operator 공부 3</title>
      <link>https://icksw.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Filtering Elements 역할을 하는 Operator들을 알아봤었습니다. 이름대로 Upstream에서 받은 값들을 어떤 조건에 의해 필터링한 뒤 Downstream으로 내려보내는 역할을 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이어서 Reducing Element로 분류된 Operator에 대해 알아보도록 하겠습니다.&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;Reducing Elements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Reducing Elements로 분류된 Publisher에는 어떤 것들이 있는지부터 알아볼게요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Collect&lt;/li&gt;
&lt;li&gt;CollectByCount&lt;/li&gt;
&lt;li&gt;CollectByTime&lt;/li&gt;
&lt;li&gt;IgnoreOutput&lt;/li&gt;
&lt;li&gt;Reduce&lt;/li&gt;
&lt;li&gt;TryReduce&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine/publishers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에는 위와 같이 6개의 Publisher가 Reducing Elements로 분류되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이를 활용해서 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;collect()&lt;/li&gt;
&lt;li&gt;collect(_:)&lt;/li&gt;
&lt;li&gt;collect(_:options:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TimeGroupingStrategy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ignoreOutput()&lt;/li&gt;
&lt;li&gt;reduce(_:_:)&lt;/li&gt;
&lt;li&gt;tryReduce(_:_:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine/publisher&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에는 위와 같이 6개의 Operator가 Reducing Elements로 분류되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 어떤 일을 하는 Publisher, Operator인지 알아볼게요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Collect&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AYSwx/btrzkyV5LQz/byUqzuNq0q3wQbzTnQ0yck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AYSwx/btrzkyV5LQz/byUqzuNq0q3wQbzTnQ0yck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AYSwx/btrzkyV5LQz/byUqzuNq0q3wQbzTnQ0yck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAYSwx%2FbtrzkyV5LQz%2FbyUqzuNq0q3wQbzTnQ0yck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;214&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 알아볼 것은 Collect입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 그냥 간단하게 아이템들을 버퍼에 가지고 있는 Publisher라고만 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼에 가지고 있어서 어쩌겠다는 건지는 이를 활용해서 만들어진 Operator를 사용해보면 알 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;collect()&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clMrQg/btrzxyvIAjI/kn66C4bEfOJBz4jZeb1kGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clMrQg/btrzxyvIAjI/kn66C4bEfOJBz4jZeb1kGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clMrQg/btrzxyvIAjI/kn66C4bEfOJBz4jZeb1kGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclMrQg%2FbtrzxyvIAjI%2Fkn66C4bEfOJBz4jZeb1kGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;271&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 모든 값을 가지고 있다가 Upstream Publisher가 finish 되면 하나의 배열에 모두 담아서 Downstream으로 보낸다고 되어있네요.&lt;/p&gt;
&lt;p 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_1649851951088&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher
intPublisher
    .collect()
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행해보면 결과는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649851974238&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Collect 예제 코드
[1, 2, 3, 4, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 정의대로 Upstream에서 받은 값을 모두 모아서 하나의 배열을 만들어 Downstream으로 보내주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collect는 Upstream에서 받는 값들을 무제한으로 모아서 Downstream으로 내려보내게 되는데, 만약 Upstream에서 받는 값이 너무 많다면 메모리에 무리가 갈 수 있습니다. 이를 보완하기 위한 CollectByCount를 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CollectByCount&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs0LbT/btrzsORIbz8/pRF39GdsV7J9DEVT3zhOTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs0LbT/btrzsORIbz8/pRF39GdsV7J9DEVT3zhOTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs0LbT/btrzsORIbz8/pRF39GdsV7J9DEVT3zhOTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs0LbT%2FbtrzsORIbz8%2FpRF39GdsV7J9DEVT3zhOTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;212&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CollectByCount는 방금 알아본 Collect과 비슷한데, 차이점은 Upstream으로부터 값을 받을 때 정해진 개수를 받을 때마다 Downstream으로 내려보낸다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 건지 이를 활용해서 만들어진 Operator를 바로 알아보죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;collect(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o034U/btrzyaaiEnV/E9Epc4iInNEpZeVYLHo2EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o034U/btrzyaaiEnV/E9Epc4iInNEpZeVYLHo2EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o034U/btrzyaaiEnV/E9Epc4iInNEpZeVYLHo2EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo034U%2FbtrzyaaiEnV%2FE9Epc4iInNEpZeVYLHo2EK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;269&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값을 정해진 숫자만큼 모아서 Downstream으로 하나의 배열로 보내는 Operator입니다.&lt;/p&gt;
&lt;p 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_1649951856631&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher
intPublisher
    .collect(2)
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 결과는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649951871446&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CollectByCount 예제 코드
[1, 2]
[3, 4]
[5]&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;정의대로 매개변수로 받은 count 개수만큼 값을 모은 뒤 해당 개수만큼 값이 모이면 하나의 배열로 만들어서 Downstream으로 내려보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 예제에서 볼 수 있듯이 마지막 값의 경우에는 count 값보다 적은 값이 모여있더라도 그냥 Downstream으로 내려보내게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CollectByTime&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzvLb/btrzpa9MdW7/OargePkOkeK7wqKi9j1MY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzvLb/btrzpa9MdW7/OargePkOkeK7wqKi9j1MY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzvLb/btrzpa9MdW7/OargePkOkeK7wqKi9j1MY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpzvLb%2Fbtrzpa9MdW7%2FOargePkOkeK7wqKi9j1MY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;236&quot; data-origin-width=&quot;958&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;다음으로 알아볼 것은 CollectByTime입니다. CollectByTime의 정의를 보면 Upstream에서 받은 값을 가지고 있다가 주기적으로 Downstream으로 내려보내는 Publisher라고 하네요. 그리고 정의에 Scheduler가 있는 걸 보면 스케줄러에 의해 주기가 정 해지는 거 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;collect(_:options:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CollectByTime을 활용해서 만든 Operator는 collect(_:options:)입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ExGs8/btrzx8wK5oG/63AJtvELoJwUSuTKMoZEqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ExGs8/btrzx8wK5oG/63AJtvELoJwUSuTKMoZEqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ExGs8/btrzx8wK5oG/63AJtvELoJwUSuTKMoZEqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FExGs8%2Fbtrzx8wK5oG%2F63AJtvELoJwUSuTKMoZEqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;312&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 strategy, options라는 매개변수가 있는데요, 사용하려면 어떤 게 필요한지 알아야 하니 어떤 타입들인지 알아볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strategy에 필요한 타입인 &lt;b&gt;TimeGroupingStratgy&lt;/b&gt;을 찾아가 보면 아래와 같네요.&lt;/p&gt;
&lt;pre id=&quot;code_1649954097794&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// A strategy for collecting received elements.
public enum TimeGroupingStrategy&amp;lt;Context&amp;gt; where Context : Scheduler {

    /// A grouping that collects and periodically publishes items.
    case byTime(Context, Context.SchedulerTimeType.Stride)

    /// A grouping that collects and publishes items periodically or when a buffer reaches a maximum size.
    case byTimeOrCount(Context, Context.SchedulerTimeType.Stride, Int)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 사용되는 Context라는 제네릭 타입은 Scheduler 타입인걸 볼 수 있습니다. 그리고 Stride도 사용하는 걸 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용할 수 있는 Scheduler에는 ImmediateScheduler가 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 케이스가 있는데, byTime은 주어진 시간 동안 받은 값을 내보내는 단순한 녀석이고, byTimeOrCount는 주어진 시간 동안 받은 값을 내보내기도 하지만, 주어진 시간동안 count 개수만큼의 값을 받으면 시간이 지나지 않더라도 내보내는 녀석입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 사용해보면 아래와 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649952530553&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscription = Set&amp;lt;AnyCancellable&amp;gt;()

let timerPublisher = Timer.publish(every: 0.5, on: .main, in: .default)
    
timerPublisher
    .autoconnect()
    .collect(.byTime(RunLoop.main, .seconds(1)))
    .sink(receiveValue: { print($0) })
    .store(in: &amp;amp;subscription)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행하면 아래와 같은 결과를 보게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649952554984&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[2022-04-14 16:07:59 +0000]
[2022-04-14 16:07:59 +0000, 2022-04-14 16:08:00 +0000]
[2022-04-14 16:08:00 +0000, 2022-04-14 16:08:01 +0000]
...&lt;/code&gt;&lt;/pre&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;아까 CollectByCount는 개수가 채워지면 Downstream으로 모아둔 값을 보냈지만, CollectByTime은 정해진 시간 동안 모인 값을 Downstream으로 내려보내고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면,&amp;nbsp;0.5초마다 값을 내보내는 timerPublisher를 만들었고 collect 메서드에서는 1초마다 모인 값들을 Downstream으로 내려보내게 만들어졌습니다. 실제로 결과를 보면 한 개의 배열에 2개의 값이 있는 것을 볼 수 있습니다. (물론 처음 거에는 1개만 있는 게 마음에 안 듭니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649954626634&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscription = Set&amp;lt;AnyCancellable&amp;gt;()

let timerPublisher = Timer.publish(every: 0.5, on: .main, in: .default)

timerPublisher
    .autoconnect()
    .collect(.byTime(DispatchQueue.main, .seconds(1)))
    .sink(receiveValue: { print($0) })
    .store(in: &amp;amp;subscription)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 DispatchQueue를 Scheduler로 사용해봤더니 결과가 아래와 같이 아주 깔끔합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649954657468&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[2022-04-14 16:48:48 +0000, 2022-04-14 16:48:49 +0000]
[2022-04-14 16:48:49 +0000, 2022-04-14 16:48:50 +0000]
[2022-04-14 16:48:50 +0000, 2022-04-14 16:48:51 +0000]
...&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;이번에는 byTimeOrCount도 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1649955328211&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscription = Set&amp;lt;AnyCancellable&amp;gt;()
let timerPublisher = Timer.publish(every: 0.5, on: .main, in: .default)

timerPublisher
    .autoconnect()
    .collect(.byTimeOrCount(DispatchQueue.main, .seconds(4), 2))
    .sink(receiveValue: { print($0) })
    .store(in: &amp;amp;subscription)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timer가 0.5초마다 값을 내보내니까 4초 동안 모아서 내보내면 8개를 모아야 하겠지만, count로 2를 줬기 때문에 2개가 모이면 바로 Downstream으로 내려보내게 됩니다. 결과를 보면 예상대로 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649955389328&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[2022-04-14 16:55:19 +0000, 2022-04-14 16:55:19 +0000]
[2022-04-14 16:55:20 +0000, 2022-04-14 16:55:20 +0000]
[2022-04-14 16:55:21 +0000, 2022-04-14 16:55:21 +0000]
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NPfBw/btrzrbUdLT4/kqeES9VIj7XpbNlMXfHKM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NPfBw/btrzrbUdLT4/kqeES9VIj7XpbNlMXfHKM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NPfBw/btrzrbUdLT4/kqeES9VIj7XpbNlMXfHKM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNPfBw%2FbtrzrbUdLT4%2FkqeES9VIj7XpbNlMXfHKM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;950&quot; height=&quot;290&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 정의를 보면 이제 strategy는 알겠고, options를 살펴봅시다. 간단하게 Scheduler의 SchedulerOptions입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 스케줄러에 따라 모두 다를 거니까, DispatchQueue의 SchedulerOptions를 살펴볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1649955658628&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct SchedulerOptions {

/// The dispatch queue quality of service.
public var qos: DispatchQoS

/// The dispatch queue work item flags.
public var flags: DispatchWorkItemFlags

/// The dispatch group, if any, that should be used for performing actions.
public var group: DispatchGroup?

public init(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], group: DispatchGroup? = nil)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;qos도 보이고 group도 설정할 수 있네요. 이렇게 스케줄러에 맞는 옵션을 설정할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IgnoreOutput&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZJgaF/btrzq6Fb6uw/O8p46BYVETketfHgQy60W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZJgaF/btrzq6Fb6uw/O8p46BYVETketfHgQy60W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZJgaF/btrzq6Fb6uw/O8p46BYVETketfHgQy60W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZJgaF%2Fbtrzq6Fb6uw%2FO8p46BYVETketfHgQy60W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;228&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 IgnoreOutput을 알아보겠습니다. 정의를 읽어보니 Upstream의 모든 값을 무시하고 Publisher의 completion만 무시하지 않는다고 되어있네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ignoreOutput()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IgnoreOutput Publisher를 활용해서 만들어진 Operator는 ignoreOutput()입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUVbFo/btrzyWQnS9C/EADvqNEuW5pLHUUgTL7Ch1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUVbFo/btrzyWQnS9C/EADvqNEuW5pLHUUgTL7Ch1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUVbFo/btrzyWQnS9C/EADvqNEuW5pLHUUgTL7Ch1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUVbFo%2FbtrzyWQnS9C%2FEADvqNEuW5pLHUUgTL7Ch1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;264&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의는 IgnoreOutput Publisher의 설명과 거의 비슷하니&amp;nbsp;바로 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1649955884451&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .ignoreOutput()
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649955908467&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// IgnoreOutput 예제 코드
// 결과
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말로 모든 값을 무시하고 completion만 출력한 것을 볼 수 있네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reduce&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으로 이번에는 Reduce를 알아봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AKzX7/btrzpDwJFA1/TCkdJPJ8Kc87usnG6uasg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AKzX7/btrzpDwJFA1/TCkdJPJ8Kc87usnG6uasg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AKzX7/btrzpDwJFA1/TCkdJPJ8Kc87usnG6uasg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAKzX7%2FbtrzpDwJFA1%2FTCkdJPJ8Kc87usnG6uasg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;226&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream에서 받은 값들을 클로저로 처리한 뒤 가지고 있다가 Upstream이 completion 되면 Downstream으로 마지막으로 처리된 값을 내려보내는 Publisher입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;reduce(_:_:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reduce로 만든 Operator는 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7dSXF/btrzv34AfaJ/VkRb5z7snsTgDjp7rcM5ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7dSXF/btrzv34AfaJ/VkRb5z7snsTgDjp7rcM5ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7dSXF/btrzv34AfaJ/VkRb5z7snsTgDjp7rcM5ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7dSXF%2Fbtrzv34AfaJ%2FVkRb5z7snsTgDjp7rcM5ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;296&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 초기값이 있는 걸 볼 수 있습니다. 역할은 Upstream에서 받은 값을 클로저로 처리해서 Upstream이 finish 되면 그동안 처리해둔 값을 Downstream으로 보낸다고 되어있습니다.&lt;/p&gt;
&lt;p 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_1649956366363&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5].publisher

intPublisher
    .reduce(0, { $0 + $1 })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 초기값이 0이고 클로저에서는 누적 값으로 현재 누적 값과 새로운 값을 더한 것을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 시작 값이 0이고 1, 2, 3, 4, 5가 계속해서 더해지며 누적되는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 그럼 당연히 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649956466681&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Reduce 예제 코드
15
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 String에도 사용해보면 이해에 도움이 될 거 같아서 아래 코드로도 해봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649956687488&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let stringPublisher = [&quot;P&quot;, &quot;I&quot;, &quot;N&quot;, &quot;G&quot;, &quot;U&quot;].publisher

stringPublisher
    .reduce(&quot;&quot;, { $0 + $1 })
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649956699554&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Reduce-String 예제 코드
PINGU
finished&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryReduce&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 벌써 이번 글의 마지막 Publisher네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이제 Try가 붙어있으면 기존 거와 동일하면서 에러가 발생하면 Downstream으로 내려보낼 수 있는 차이밖에 없을 거라는 걸 느낍니다. &lt;/p&gt;
&lt;p 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;966&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r73lW/btrzoO0fEMi/th6X7o3UIdugESbq0TuecK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r73lW/btrzoO0fEMi/th6X7o3UIdugESbq0TuecK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r73lW/btrzoO0fEMi/th6X7o3UIdugESbq0TuecK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr73lW%2FbtrzoO0fEMi%2Fth6X7o3UIdugESbq0TuecK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;210&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 예상대로입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryReduce(_:_:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryReduce를 사용해서 만든 Operator는 tryReduce(_:_:)입니다.&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;668&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vCAZf/btrzyYgmA0P/aEL1BfqLX2zr2aH3xTayfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vCAZf/btrzyYgmA0P/aEL1BfqLX2zr2aH3xTayfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vCAZf/btrzyYgmA0P/aEL1BfqLX2zr2aH3xTayfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvCAZf%2FbtrzyYgmA0P%2FaEL1BfqLX2zr2aH3xTayfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;286&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 예상대로 reduce(_:_:)에서 에러도 던질 수 있다는 차이만 존재합니다.&lt;/p&gt;
&lt;p 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_1649957105283&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case nagativeNumber
}
func checkNagativeNumber(_ number: Int) throws -&amp;gt; Int {
    guard number &amp;lt; 0 else {
        throw PinguError.nagativeNumber
    }
    return number
}

let intPublisher = [1, 2, 3, -10, 4].publisher

intPublisher
    .tryReduce(0) { reduceValue, newValue in
        try checkNagativeNumber(reduceValue + newValue)
        return reduceValue + newValue
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 초기값이 0인 상태에서 Upstream에서 받은 정수 값을 계속해서 누적 값에 더하는데 만약 누적 값이 음수가 된다면 에러를 발생시키도록 만든 코드입니다. 1, 2, 3까지는 더하다가 -10을 더하면 음수가 되니까 거기서 에러가 발생하겠네요.&lt;/p&gt;
&lt;p 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_1649957165918&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TryReduce 예제 코드
failure(__lldb_expr_73.(unknown context at $10c030734).(unknown context at $10c03073c).(unknown context at $10c030744).PinguError.nagativeNumber)&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;이렇게 Combine의 Operator 중 Reducing Operator로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icksw.tistory.com/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Applying Mathematical Operations on Elements라고 분류된 것들에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/ReducingElementsOperator.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>collect</category>
      <category>collectByCount</category>
      <category>collectByTime</category>
      <category>Combine</category>
      <category>IgnoreOutput</category>
      <category>operator</category>
      <category>reduce</category>
      <category>Swift</category>
      <category>TimeGroupingStrategy</category>
      <category>tryreduce</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/279</guid>
      <comments>https://icksw.tistory.com/279#entry279comment</comments>
      <pubDate>Fri, 15 Apr 2022 02:31:26 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Filtering Elements Operator - Operator 공부 2</title>
      <link>https://icksw.tistory.com/278</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/277&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine의 Operator 중 Mapping Element 역할을 하는 녀석들을 알아봤었습니다. Map, TryMap, MapError, Scan, TryScan, SetFailureType이 Mapping Element로 분류된 Operator 들이었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이어서 Filtering Element로 분류된 Operator에 대해 알아보도록 하겠습니다.&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;Filtering Elements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 보면 뭔가를 필터링해줄 거 같은데요, 여기에 분류된 Publisher는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Filter&lt;/li&gt;
&lt;li&gt;TryFilter&lt;/li&gt;
&lt;li&gt;CompactMap&lt;/li&gt;
&lt;li&gt;TryCompactMap&lt;/li&gt;
&lt;li&gt;RemoveDuplicates&lt;/li&gt;
&lt;li&gt;TryRemoveDuplicates&lt;/li&gt;
&lt;li&gt;ReplaceEmpty&lt;/li&gt;
&lt;li&gt;ReplaceError&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 8개의 Publisher가 Filtering Element로 분류되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이를 활용해서 만들어진 Operator는 아래와 같이 9개입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;filter(_:)&lt;/li&gt;
&lt;li&gt;tryFilter(_:)&lt;/li&gt;
&lt;li&gt;compactMap(_:)&lt;/li&gt;
&lt;li&gt;tryCompactMap(_:)&lt;/li&gt;
&lt;li&gt;removeDuplicates()&lt;/li&gt;
&lt;li&gt;removeDuplicates(by:)&lt;/li&gt;
&lt;li&gt;tryRemoveDuplicates(by:)&lt;/li&gt;
&lt;li&gt;replaceEmpty(with:)&lt;/li&gt;
&lt;li&gt;replaceError(with:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 이렇게 보면 여러 개 있는 거 같은데 지난 글에서 알아본 Operator와 같이 몇 개는 Try만 붙어서 에러를 처리만 할 수 있을 뿐 동일한 역할을 할 거 같긴 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Filtering이라는 이름에서 느낄 수 있는 것은 Upstream에서 받은 값들을 필터링해서 Downstream으로 전달하는 역할을 할 것 같습니다.&lt;/p&gt;
&lt;p 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;Filter&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZdVTG/btryF84DInM/6I49abu5VPydpjFQMsd4k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZdVTG/btryF84DInM/6I49abu5VPydpjFQMsd4k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZdVTG/btryF84DInM/6I49abu5VPydpjFQMsd4k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZdVTG%2FbtryF84DInM%2F6I49abu5VPydpjFQMsd4k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;214&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 제공된 클로저를 사용해서 모든 element를 조건에 맞는 것만 내보내는 publisher라고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 그냥 전달받은 애들 중에 조건에 맞는 애들만 다시 내보내는 녀석입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;filter(_:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Filter Publisher를 활용해서 만들어진 Operator는 filter(_:)입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7pFuD/btrzz4UI1PA/IXo2IZPdwZTYcZBe3OgXHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7pFuD/btrzz4UI1PA/IXo2IZPdwZTYcZBe3OgXHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7pFuD/btrzz4UI1PA/IXo2IZPdwZTYcZBe3OgXHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7pFuD%2Fbtrzz4UI1PA%2FIXo2IZPdwZTYcZBe3OgXHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;256&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값 중 클로저를 통과한 모든 값을 Downstream으로 내려보낸다고 되어있습니다.&lt;/p&gt;
&lt;p 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_1649255884636&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5, 6, 7].publisher
intPublisher
    .filter { element in
        return element % 2 == 0
    }
    .sink { print($0) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 짝수만 downstream으로 보내도록 만든 것을 볼 수 있는데요, 실제로 실행하면 결과는 아래와 같습니다!&lt;/p&gt;
&lt;pre id=&quot;code_1649255931768&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Filter 예제 코드
2
4
6&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryFilter&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Filter에 Try가 붙은 TryFilter도 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/85KWA/btryF9oWcW7/68jGEdkkMLAKeWYBGkKY1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/85KWA/btryF9oWcW7/68jGEdkkMLAKeWYBGkKY1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/85KWA/btryF9oWcW7/68jGEdkkMLAKeWYBGkKY1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F85KWA%2FbtryF9oWcW7%2F68jGEdkkMLAKeWYBGkKY1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;208&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 그냥 Filter인데 에러를 던질 수 있다는 차이점만 가지고 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryFilter(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lD5K9/btrzwf39yO5/aspMN5fFBEwDP3qihqNFm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lD5K9/btrzwf39yO5/aspMN5fFBEwDP3qihqNFm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lD5K9/btrzwf39yO5/aspMN5fFBEwDP3qihqNFm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlD5K9%2Fbtrzwf39yO5%2FaspMN5fFBEwDP3qihqNFm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;272&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryFilter Publisher로 만들어진 Operator는 tryFilter(_:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 역시나 filter(_:)와 동일한데 에러를 던질 수 있다는 차이점밖에 없네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1649256308573&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case oddNumber
}

func checkEvenNumber(_ number: Int) throws -&amp;gt; Bool {
    guard number % 2 == 0 else {
        throw PinguError.oddNumber
    }
    return true
}

let intPublisher = [2, 2, 4, 4, 5, 6].publisher
intPublisher
    .tryFilter { element in
        try checkEvenNumber(element)
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished:
            print(&quot;모두 짝수네용&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼 코드를 작성하면 짝수일 때는 downstream으로 값을 내려보내고, 짝수가 아닐 때는 에러를 던지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649256390640&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TryFilter 예제 코드
2
2
4
4
The operation couldn&amp;rsquo;t be completed. (__lldb_expr_9.(unknown context at $1093875b4).(unknown context at $1093875bc).(unknown context at $1093875c4).PinguError error 0.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 2나 4는 짝수라서 잘 내려보내다가, 5에서 에러가 던져져서 실패로 끝난 것을 볼 수 있어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CompactMap&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 CompactMap을 살펴볼게요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVT8jC/btryCldyzzW/x7JZ1iGuiQN6xzHcXzQUSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVT8jC/btryCldyzzW/x7JZ1iGuiQN6xzHcXzQUSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVT8jC/btryCldyzzW/x7JZ1iGuiQN6xzHcXzQUSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVT8jC%2FbtryCldyzzW%2Fx7JZ1iGuiQN6xzHcXzQUSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;228&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면.. upstream에서 받은 element를 클로저에 전달해서 처리된 값이 nil이 아닌 값들만 downstream으로 내려보내는 publisher인 듯합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 클로저에서 뭔가 작업을 해서 nil이면 downstream에 전달하지 않는 그런 녀석 같네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;compactMap(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HQ9mG/btrzxyJe1Zx/PlWUDV1VUawDeCXfxXbfpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HQ9mG/btrzxyJe1Zx/PlWUDV1VUawDeCXfxXbfpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HQ9mG/btrzxyJe1Zx/PlWUDV1VUawDeCXfxXbfpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHQ9mG%2FbtrzxyJe1Zx%2FPlWUDV1VUawDeCXfxXbfpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;279&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CompactMap Publisher를 활용해서 만든 Operator는 compactMap(_:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 값을 클로저로 처리된 값이 nil이 아닌 값만 Downstream으로 내려보내는 Operator인 듯하네요.&lt;/p&gt;
&lt;p 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_1649256680843&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let stringPublisher = [&quot;1&quot;, &quot;2&quot;, &quot;a.b&quot;, &quot;3&quot;].publisher
stringPublisher
    .compactMap { element in
        return Int(element)
    }
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 String Publisher를 만들어서 각각의 값들을 Int로 변환을 시도하는 것을 볼 수 있습니다. Int로 변환이 안 되는 String 값의 경우엔 nil이 반환되니까 아까 CompactMap의 정의대로라면 그런 값은 downstream으로 전달되지 못하겠네요.&lt;/p&gt;
&lt;p 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_1649256870027&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CompactMap 예제 코드
1
2
3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오~ downstream으로 전달된 값 중에는 &quot;a.b&quot;라는 String 값이 없는 것을 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의대로 잘 동작하는 것을 알 수 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryCompactMap&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘도 뭐 이름을 보니 CompactMap이랑 똑같은데 에러만 던지는 Publisher일 거 같네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kByO0/btryCQj8ctY/rbGTEk1LlNyjYcmmZ0mP81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kByO0/btryCQj8ctY/rbGTEk1LlNyjYcmmZ0mP81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kByO0/btryCQj8ctY/rbGTEk1LlNyjYcmmZ0mP81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkByO0%2FbtryCQj8ctY%2FrbGTEk1LlNyjYcmmZ0mP81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;230&quot; data-origin-width=&quot;964&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;역시나 정의를 보니 그렇습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryCompactMap(_:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryCompactMap Publisher를 활용해서 만들어진 Operator는 tryCompactMap(_:)입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnErlv/btrzz5lOgqw/mkjVg3MsqnF0WBOLg0omp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnErlv/btrzz5lOgqw/mkjVg3MsqnF0WBOLg0omp1/img.png&quot; data-alt=&quot;'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnErlv/btrzz5lOgqw/mkjVg3MsqnF0WBOLg0omp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnErlv%2Fbtrzz5lOgqw%2FmkjVg3MsqnF0WBOLg0omp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;280&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;280&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;정의를 보면 역시나 compactMap(_:)과 똑같은데 에러만 던질 수 있다고 되어있습니다.&lt;/p&gt;
&lt;p 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_1649257201165&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case thisIsNil
}

func checkTranformIntAvailable(string: String) throws -&amp;gt; Int {
    guard let intValue = Int(string) else {
        throw PinguError.thisIsNil
    }
    return intValue
}

let stringPublisher = [&quot;1&quot;, &quot;2&quot;, &quot;a.b&quot;, &quot;3&quot;].publisher
stringPublisher
    .tryCompactMap { element in
        try checkTranformIntAvailable(string: element)
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished:
            print(&quot;모두 Int로 변환가능해요&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 아까 전에 봤던 compactMap 예제 코드에 nil이 나오면 이번에는 에러를 던지게 코드를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 예상대로 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649257245747&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TryCompactMap 예제 코드
1
2
The operation couldn&amp;rsquo;t be completed. (__lldb_expr_15.(unknown context at $107d139dc).(unknown context at $107d13a1c).(unknown context at $107d13a24).PinguError error 0.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;a.b&quot;라는 녀석은 Int로 변환을 못해서 에러를 던지는 것을 볼 수 있네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RemoveDuplicates&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 RemoveDuplicates라는 Publisher를 살펴보겠습니다.&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;956&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bthnyy/btryC94vuqi/CPDcr09XiUpQmVFnxWPxMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bthnyy/btryC94vuqi/CPDcr09XiUpQmVFnxWPxMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bthnyy/btryC94vuqi/CPDcr09XiUpQmVFnxWPxMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbthnyy%2FbtryC94vuqi%2FCPDcr09XiUpQmVFnxWPxMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;206&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 이전에 내보낸 값과 일치하지 않는 값만 downstream에 전달하는 publisher라고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 조금 헷갈릴 수 있는데, 바로 직전에 내보낸 값과 일치하는지만 확인해주는 건데요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 활용해서 만들어진 Operator를 보면 이해가 잘 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;removeDuplicates()&lt;/h3&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;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhAEzo/btrzv4PVyRy/gn8vAWT6dcNBcO70Cgd821/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhAEzo/btrzv4PVyRy/gn8vAWT6dcNBcO70Cgd821/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhAEzo/btrzv4PVyRy/gn8vAWT6dcNBcO70Cgd821/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhAEzo%2Fbtrzv4PVyRy%2Fgn8vAWT6dcNBcO70Cgd821%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;265&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoveDuplicates Publisher를 활용해서 만들어진 Operator는 2개인데 그중 하나는 removeDuplicates()입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream에서 받은 바로 직전의 값과 같지 않은 값을 Downstream으로 내려보낸다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 같은지 확인을 해야 하니 Output은 Equatable 프로토콜을 준수하는 타입이어야 합니다.&lt;/p&gt;
&lt;p 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_1649257503003&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 1, 1, 2, 2, 3].publisher
intPublisher
    .removeDuplicates()
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649257544822&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RemoveDuplicates 예제 코드
1
2
3
1
2
3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 위 코드를 보면 [1, 2, 3, 1, 1, 2, 2, 3]를 publish 하는데요, 여기서 Set과 같이 [1, 2, 3]만 남는 게 아니고 바로 직전의 값과 동일한 값만 필터링돼서 사라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 5번째 1과 7번째 2만 사라진 것을 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 RemoveDuplicates Publisher의 구현을 살펴보면..&lt;/p&gt;
&lt;pre id=&quot;code_1649257769263&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct RemoveDuplicates&amp;lt;Upstream&amp;gt; : Publisher where Upstream : Publisher {
    public typealias Output = Upstream.Output

    public typealias Failure = Upstream.Failure

    public let upstream: Upstream

    public let predicate: (Publishers.RemoveDuplicates&amp;lt;Upstream&amp;gt;.Output, Publishers.RemoveDuplicates&amp;lt;Upstream&amp;gt;.Output) -&amp;gt; Bool

    public init(upstream: Upstream, predicate: @escaping (Publishers.RemoveDuplicates&amp;lt;Upstream&amp;gt;.Output, Publishers.RemoveDuplicates&amp;lt;Upstream&amp;gt;.Output) -&amp;gt; Bool)

    public func receive&amp;lt;S&amp;gt;(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되어있는데요, predicate라는 프로퍼티가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘는 개발자가 직접 원하는 대로 중복 값을 처리할 수 있도록 클로저를 구현하게 해 주는데요, 클로저에서 true를 반환하면 중복으로 처리해서 downstream으로 전달하지 않고 false를 반환해야 중복이 아니라고 판단해서 downstream으로 전달하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 사용하는 방법은 removeDuplicates(by:)를 사용하면 되는데요, 바로 알아보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;removeDuplicates(by:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YAdVu/btrzveSoNtp/fkx0UmGRswvDKyRsObuSM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YAdVu/btrzveSoNtp/fkx0UmGRswvDKyRsObuSM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YAdVu/btrzveSoNtp/fkx0UmGRswvDKyRsObuSM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYAdVu%2FbtrzveSoNtp%2Ffkx0UmGRswvDKyRsObuSM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;299&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;removeDuplicates(by:)의 정의는 위와 같습니다. Upstream에서 바로 직전에 받은 값을 &lt;span&gt;제공된 클로저로&lt;span&gt;&amp;nbsp;현재 값과&lt;/span&gt;&lt;/span&gt;&amp;nbsp;같은지 비교해서 Downstream으로 내려보낸다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 비교 로직을 직접 구현할 수 있어서 아까 전의 removeDuplicates() 다르게 Output이 Equatable 프로토콜을 준수하지 않아도 됩니다.&lt;/p&gt;
&lt;p 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_1649258083459&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Name {
    let lastName: String
    let firstName: String

    func printName() {
        print(lastName + firstName)
    }
}

let namePublisher = [
    Name(lastName: &quot;Pin&quot;, firstName: &quot;gu&quot;),
    Name(lastName: &quot;Pin&quot;, firstName: &quot;ga&quot;),
    Name(lastName: &quot;Ro&quot;, firstName: &quot;By&quot;),
    Name(lastName: &quot;O&quot;, firstName: &quot;dung&quot;)
].publisher

namePublisher
    .removeDuplicates(by: { prev, current in
        return prev.lastName == current.lastName
    })
    .sink(receiveValue: { $0.printName() })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1649258727932&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RemoveDuplicates(by:) 예제 코드
Pingu
RoBy
Odung&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성과 이름을 저장해주는 Name이라는 구조체를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 removeDublicates의 중복확인 로직으로 바로 직전 값과 lastName이 같으면 중복으로 처리하도록 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위와 같이 &quot;Pin&quot;이라는 lastName이 연속으로 나와서 뒤에 publish 된 &quot;Pin ga&quot;는 downstream으로 전달되지 못한 것을 볼 수 이 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TryRemoveDuplicates&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Try만 붙어도 에러를 내보내는 기능만 추가된 Publisher라는 걸 알 수 있겠네요. &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBmLjq/btryCERf2Zk/uc7cBAWEPEteLdf5GLgbkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBmLjq/btryCERf2Zk/uc7cBAWEPEteLdf5GLgbkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBmLjq/btryCERf2Zk/uc7cBAWEPEteLdf5GLgbkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBmLjq%2FbtryCERf2Zk%2Fuc7cBAWEPEteLdf5GLgbkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;228&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 정의를 봐도 그렇습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryRemoveDuplicates(by:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QO0f2/btrzwl4YDuH/WCO5w11cdGRR5yVzhJwkKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QO0f2/btrzwl4YDuH/WCO5w11cdGRR5yVzhJwkKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QO0f2/btrzwl4YDuH/WCO5w11cdGRR5yVzhJwkKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQO0f2%2Fbtrzwl4YDuH%2FWCO5w11cdGRR5yVzhJwkKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;292&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryRemoveDuplicates Publisher를 활용해서 만들어진 tryRemoveDuplicates(by:)의 정의를 봐도 아까 알아본 removeDuplicates(by:)와 동일하지만 에러만 던질 수 있다는 차이가 있네요.&lt;/p&gt;
&lt;p 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_1649259926151&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case duplicate
}

let intPublisher = [1, 2, 2, 3, 3].publisher
intPublisher
    .tryRemoveDuplicates { prev, current in
        if prev == current {
            throw PinguError.duplicate
        }
        return prev == current
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished:
            print(&quot;중복값 없음&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 현재 값을 바로 직전 값과 비교했을 때 같다면 downstream으로 내려보내지 않을 뿐 아니라 에러를 던지도록 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1649259982502&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TryRemoveDuplicates 예제 코드
1
2
The operation couldn&amp;rsquo;t be completed. (__lldb_expr_47.(unknown context at $100fcd214).(unknown context at $100fcd2b4).(unknown context at $100fcd2bc).PinguError error 0.)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ReplaceEmpty&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 ReplaceEmpty입니다. 뭔가 비어있는 것을 대체하는 역할을 할 거 같은데, 맞는지 알아볼게요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFR9sQ/btryCk6VAI2/PWfcDplTxBShPoQVrX4dN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFR9sQ/btryCk6VAI2/PWfcDplTxBShPoQVrX4dN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFR9sQ/btryCk6VAI2/PWfcDplTxBShPoQVrX4dN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFR9sQ%2FbtryCk6VAI2%2FPWfcDplTxBShPoQVrX4dN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;208&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 빈 스트림을 전달받으면 제공된 element로 바꿔주는 역할을 하는 Publisher라고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;replaceEmpty(with:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GXcjw/btrzwQRcjNK/PIeeFdj9zggjjvkipgWq71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GXcjw/btrzwQRcjNK/PIeeFdj9zggjjvkipgWq71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GXcjw/btrzwQRcjNK/PIeeFdj9zggjjvkipgWq71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGXcjw%2FbtrzwQRcjNK%2FPIeeFdj9zggjjvkipgWq71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;228&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReplaceEmpty Publisher를 활용해서 만들어진 replaceEmpty(with:)의 정의를 보면 간단하게 Upstream에서 빈 stream을 받으면 매개변수로 받은 값으로 대체해서 Downstream으로 보내는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p 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_1649260436219&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [].publisher
intPublisher
    .replaceEmpty(with: 5)
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 배열이 전달되면 5로 바꿔주도록 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 결과는 당연히..&lt;/p&gt;
&lt;pre id=&quot;code_1649260529421&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReplaceEmpty 예제 코드
5&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ReplaceError&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 오늘 알아볼 마지막 Publisher인 ReplaceError를 알아보겠습니다.&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;958&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ukG1/btryzBHwSBb/yG47gclPyF1xedvlrtTXU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ukG1/btryzBHwSBb/yG47gclPyF1xedvlrtTXU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ukG1/btryzBHwSBb/yG47gclPyF1xedvlrtTXU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ukG1%2FbtryzBHwSBb%2FyG47gclPyF1xedvlrtTXU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;206&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실제 정의를 봐도 그렇습니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;replaceError(with:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xa82W/btrzzpSnUBJ/eWdTPAGqWuz5KJ4RIVKnLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xa82W/btrzzpSnUBJ/eWdTPAGqWuz5KJ4RIVKnLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xa82W/btrzzpSnUBJ/eWdTPAGqWuz5KJ4RIVKnLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXa82W%2FbtrzzpSnUBJ%2FeWdTPAGqWuz5KJ4RIVKnLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;233&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReplaceError Publisher를 활용해서 만들어진 Operator는 replaceError(with:)입니다.&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;그럼 아까 해본 예제 중에서 String을 Int로 바꾸던 예제를 다시 활용해서 ReplaceError를 사용하는 예제를 만들어볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1649260841702&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case thisIsNil
}

func checkTranformIntAvailable(string: String) throws -&amp;gt; Int {
    guard let intValue = Int(string) else {
        throw PinguError.thisIsNil
    }
    return intValue
}

let stringPublisher = [&quot;1&quot;, &quot;2&quot;, &quot;a.b&quot;, &quot;3&quot;].publisher
stringPublisher
    .tryCompactMap { element in
        try checkTranformIntAvailable(string: element)
    }
    // Error를 0으로 모두 대체
    .replaceError(with: 0)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished:
            print(&quot;모두 Int로 변환가능해요&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1649260867585&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReplaceError 예제 코드
1
2
0
모두 Int로 변환가능해요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;a.b&quot;를 Int로 변환하려고 하면 에러를 발생시켰는데, ReplaceError를 사용해서 에러가 발생할 경우 0으로 바꿔주도록 했더니 completion도 finished로 처리된 것을 볼 수 있습니다.&lt;/p&gt;
&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/combine/publisher/replaceerror(with:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에 따르면 replaceError는 하나의 Element를 보내고 스트림을 종료해서 에러를 처리하려는 경우에 유용하다고 하네요. catch라는 Operator를 사용해서 에러를 처리해주는 게 좋다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 위 예제가 여러 개의 Element를 처리하고 있으니 간단하게 catch로도 처리해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1649261417273&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case thisIsNil
}

func checkTranformIntAvailable(string: String) throws -&amp;gt; Int {
    guard let intValue = Int(string) else {
        throw PinguError.thisIsNil
    }
    return intValue
}

let stringPublisher = [&quot;1&quot;, &quot;2&quot;, &quot;a.b&quot;, &quot;3&quot;].publisher
stringPublisher
    .tryCompactMap { element in
        try checkTranformIntAvailable(string: element)
    }
    // Error를 0으로 모두 대체
    .catch { error in
        Just(0)
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished:
            print(&quot;모두 Int로 변환가능해요&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 아까와 동일합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649261435525&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReplaceError With Catch 예제 코드
1
2
0
모두 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;이렇게 Combine의 Operator 중 Filtering Operator로 분류된 것들에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 &lt;a href=&quot;https://icksw.tistory.com/279&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Reducing Elements&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;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/FilteringElementsOperator.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>compactmap</category>
      <category>Filter</category>
      <category>IOS</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>RemoveDuplicates</category>
      <category>ReplaceEmpty</category>
      <category>ReplaceError</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/278</guid>
      <comments>https://icksw.tistory.com/278#entry278comment</comments>
      <pubDate>Thu, 7 Apr 2022 01:13:35 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Mapping Elements Operator - Operator 공부 1</title>
      <link>https://icksw.tistory.com/277</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/276&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;... 이 좀 오래됐는데, 어쨌든 지난 글에서는 Combine의 Publisher, Subscriber를 연결하는 Subscription에 대해 알아봤었는데, 이번 글에서는 드디어 Operator에 대해 알아보려고 합니다.&lt;/p&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에 Opeartor들이 얼마나 많은지...; 근데 그럼에도 불구하고 부족한 게 많아서 직접 만들어서 써야 하는 것들이 많은 거 같더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본부터 하자는 생각에 Apple에서 미리 만들어둔 Operator들을 먼저 공부해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Apple의 &lt;a href=&quot;https://developer.apple.com/documentation/combine/publishers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Publishers 공식문서&lt;/a&gt;에 보면 미리 구현해둔 Operator들이 모두 정리되어있는데요, 그중에 Mapping Elements라는 녀석들부터 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Operator란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Operator들은 Publisher의 extension에 구현되어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2llob/btrvkFSqL3l/pyBrlA5b125Q0xl1N6s9ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2llob/btrvkFSqL3l/pyBrlA5b125Q0xl1N6s9ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2llob/btrvkFSqL3l/pyBrlA5b125Q0xl1N6s9ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2llob%2FbtrvkFSqL3l%2FpyBrlA5b125Q0xl1N6s9ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;243&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 정의를 봐도 Publisher의 extension에 여러 개의 Operator들이 메서드로 구현되어있다고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 여기에 구현된 Operator역할을 하는 메서드들이 반환하는 타입이 Publishers에 구현되어있습니다. (&quot;s&quot;가 붙은 차이점을 잘 봐야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operator를 사용하면 Upstream에게서 값을 받아서 원하는 대로 처리한 뒤 Downstream에 값을 내려보낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 지금부터 알아볼 미리 구현된 Operator들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;어떤 일을 하는지 미리 정해져 있습니다. (물론 직접 Operator를 만들 수도 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다양한 역할을 하는 여러 개의 Operator들을 활용해서 원하는 값을 만들어낼 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Operator인 이유는&amp;nbsp;이후에 알아볼 Map이라는 Operator의 구현에서도 알 수 있습니다...&lt;/p&gt;
&lt;pre id=&quot;code_1646666630684&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct Map&amp;lt;Upstream, Output&amp;gt; : Publisher where Upstream : Publisher {
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현에 나와있듯이 &lt;b&gt;Operator의 반환 타입으로 사용할 수 있는 Publisher는 Upstream이 반드시 Publisher&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;그래서 이번 글에서는 Apple이 구현해둔 많은 Operator들 중 Mapping Elements라고 분류된 녀석들에 대해 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Mapping Elements&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mapping Elements Operator가 반환하는 Publisher에는 아래와 같은 타입들이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Map&lt;/li&gt;
&lt;li&gt;TryMap&lt;/li&gt;
&lt;li&gt;MapError&lt;/li&gt;
&lt;li&gt;Scan&lt;/li&gt;
&lt;li&gt;TryScan&lt;/li&gt;
&lt;li&gt;SetFailureType&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위의 6개의 Publisher를 반환하는 Operator는 아래와 같이 7개입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map(_:)&lt;/li&gt;
&lt;li&gt;tryMap(_:)&lt;/li&gt;
&lt;li&gt;mapError(_:)&lt;/li&gt;
&lt;li&gt;replaceNil(with:)&lt;/li&gt;
&lt;li&gt;scan(_:_:)&lt;/li&gt;
&lt;li&gt;tryScan(_:_:)&lt;/li&gt;
&lt;li&gt;setFailureType(to:)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 방금 언급한 Publisher들과 Operator들을 알아보도록 하겠습니다!&lt;/p&gt;
&lt;h1&gt;Map&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brGHcb/btrvaIjic6j/XGzkDPICrnjeCxpCqKdPg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brGHcb/btrvaIjic6j/XGzkDPICrnjeCxpCqKdPg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brGHcb/btrvaIjic6j/XGzkDPICrnjeCxpCqKdPg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrGHcb%2FbtrvaIjic6j%2FXGzkDPICrnjeCxpCqKdPg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;230&quot; data-origin-width=&quot;960&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;정의를 보면 제공된 클로저를 사용해서 Upstream Publisher의 모든 element를 변환하는 Publisher라고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 바로 Map이라는 Publisher를 활용해서 만든 Operator를 알아볼게요.&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;map(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dglp4G/btrzL9n8T1g/lYrIefAnLPQdvVIK75XrlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dglp4G/btrzL9n8T1g/lYrIefAnLPQdvVIK75XrlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dglp4G/btrzL9n8T1g/lYrIefAnLPQdvVIK75XrlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdglp4G%2FbtrzL9n8T1g%2FlYrIefAnLPQdvVIK75XrlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;250&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map(_:)의 정의를 보면 Publishers.Map 타입을 반환하는 것을 볼 수 있습니다. 그리고 역할은 Upstream에서 받은 모든 값을 제공된 클로저로 변환해서 Downstream으로 보내는 역할을 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 기본 제공 함수 중에도 map이 있었는데, 얘도 비슷하게 작동하는 거 같아요.&lt;/p&gt;
&lt;p 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_1646584767673&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intPublisher = [1, 2, 3, 4, 5, 6, 7].publisher
intPublisher
    .map { element in
        return element * 2
    }
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 결과는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646584817524&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Map 코드 결과
2
4
6
8
10
12
14&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Map은 upstream publisher이 값을 내보내면 그걸 받아서 자신의 클로저에 구현된 대로 수행한 뒤에 downstream으로 보내는 역할을 합니다. 위 코드에서는 받은 값을 2배로 증가시켜서 downstream으로 보냈으니 결과가 위와 같은 것입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map에는 key path를 사용해서 1~3개의 프로퍼티에 매핑할 수 있게 만들 수도 있는데요, 간단하게 사용해보면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646585450994&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Point {
    let x: Int
    let y: Int
    let z: Int
}

let publisher = PassthroughSubject&amp;lt;Point, Never&amp;gt;()

publisher
    .map(\.x, \.y, \.z)
    .sink(receiveValue: { x, y, z in
        print(&quot;x: \(x), y: \(y), z: \(z)&quot;)
    })

publisher.send(Point(x: 1, y: 2, z: 3))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;4개는 외 않&amp;nbsp;돼?&quot;라고 생각하신다면,, 미리 정의된 거에는 다음과 같이 3개까지 밖에 없어서 그렇습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646585647596&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func map&amp;lt;T&amp;gt;(_ keyPath: KeyPath&amp;lt;Self.Output, T&amp;gt;) -&amp;gt; Publishers.MapKeyPath&amp;lt;Self, T&amp;gt;
public func map&amp;lt;T0, T1&amp;gt;(_ keyPath0: KeyPath&amp;lt;Self.Output, T0&amp;gt;, _ keyPath1: KeyPath&amp;lt;Self.Output, T1&amp;gt;) -&amp;gt; Publishers.MapKeyPath2&amp;lt;Self, T0, T1&amp;gt;
public func map&amp;lt;T0, T1, T2&amp;gt;(_ keyPath0: KeyPath&amp;lt;Self.Output, T0&amp;gt;, _ keyPath1: KeyPath&amp;lt;Self.Output, T1&amp;gt;, _ keyPath2: KeyPath&amp;lt;Self.Output, T2&amp;gt;) -&amp;gt; Publishers.MapKeyPath3&amp;lt;Self, T0, T1, T2&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개부터는 직접 만들어야 할 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 어쨌든 Map은 간단하게 여기까지만 알아보면 될 거 같네요!&lt;/p&gt;
&lt;h1&gt;TryMap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 TryMap입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryMap은 이름부터 map이랑 똑같을 거면서 try를 사용해서 에러를 downstream으로 보낼 수 있을 것만 같이 생겼습니다.&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;954&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQZLTR/btru9p4cYRF/2pNrKOPcWRE6o1XmFUX7Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQZLTR/btru9p4cYRF/2pNrKOPcWRE6o1XmFUX7Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQZLTR/btru9p4cYRF/2pNrKOPcWRE6o1XmFUX7Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQZLTR%2Fbtru9p4cYRF%2F2pNrKOPcWRE6o1XmFUX7Zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;228&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryMap(_:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryMap을 활용해서 만든 Operator는 tryMap(_:)입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qbAy7/btrzMIDQ3YR/PZT0loKcJVR4WIKhyPGDgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qbAy7/btrzMIDQ3YR/PZT0loKcJVR4WIKhyPGDgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qbAy7/btrzMIDQ3YR/PZT0loKcJVR4WIKhyPGDgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqbAy7%2FbtrzMIDQ3YR%2FPZT0loKcJVR4WIKhyPGDgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;656&quot; height=&quot;278&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할은 아까 알아본 map(_:)에서 에러도 던질 수 있다는 차이밖에 없습니다.&lt;/p&gt;
&lt;p 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_1646586822795&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case elementIsNil
}
func checkNil(element: Int?) throws -&amp;gt; Int {
    guard let element = element else {
        throw PinguError.elementIsNil
    }
    return element
}

let publisher = [1, 2, nil, 4].publisher
publisher
    .tryMap { try checkNil(element: $0) }
    .sink(receiveCompletion: {
        switch $0 {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished:
            print(&quot;끝~&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 에러를 downstream으로 보낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 위 코드는 에러를 보내게 되는데요, 결과는 다음과 같이 나옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646586861995&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
2
The operation couldn&amp;rsquo;t be completed. (__lldb_expr_39.(unknown context at $107ab7534).(unknown context at $107ab7570).(unknown context at $107ab7578).PinguError error 0.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 publisher가 가지고 있던 값은 4개인데, 3번째 element에서 에러를 받으면 failure를 받아 publish가 끝나서 4번째 값은 방출이 안된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h1&gt;MapError&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sUsJo/btrviHQwzBJ/PUiG2I1v0uX7vuBpcLJAl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sUsJo/btrviHQwzBJ/PUiG2I1v0uX7vuBpcLJAl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sUsJo/btrviHQwzBJ/PUiG2I1v0uX7vuBpcLJAl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsUsJo%2FbtrviHQwzBJ%2FPUiG2I1v0uX7vuBpcLJAl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;238&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 TryMap에서는 받아온 에러의 타입을 바꾸는 건 하지 못했는데요, MapError를 사용하면 upstream에서 발생한 에러를 받아서 원하는 에러 타입에 매핑할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;mapError(_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsZTom/btrzKnAqAB4/GzwOgkk5ucktPCPLxlWndk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsZTom/btrzKnAqAB4/GzwOgkk5ucktPCPLxlWndk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsZTom/btrzKnAqAB4/GzwOgkk5ucktPCPLxlWndk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsZTom%2FbtrzKnAqAB4%2FGzwOgkk5ucktPCPLxlWndk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;251&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MapError Publisher를 활용해서 만든 mapError(_:)는 Upstream에서 받은&amp;nbsp;failure를 새로운 에러로 바꾸는 것이라고 되어있네요.&lt;/p&gt;
&lt;p 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_1646588882879&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum AnyError: Error {
    case any
}

enum PinguError: Error {
    case elementIsNil
}

func checkNil(element: Int?) throws -&amp;gt; Int {
    guard let element = element else {
        throw AnyError.any
    }
    return element
}

let publisher = [1, 2, nil, 4].publisher
publisher
    .tryMap { try checkNil(element: $0) }
    .mapError { $0 as? PinguError ?? .elementIsNil }
    .sink(receiveCompletion: {
        switch $0 {
        case .failure(let error):
            if error == .elementIsNil {
                print(&quot;elementIsNil 에러&quot;)
            } else {
                print(error.localizedDescription)
            }
        case .finished:
            print(&quot;끝~&quot;)
        }
    }, receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 코드와 거의 비슷한데, 이번에는 AnyError라는 타입도 추가되었고, checkNil(element:) 함수에서 값이 nil일 때 AnyError가 반환됩니다.&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;그런 뒤 아까와 마찬가지로 tryMap을 사용해서 downstream으로 에러를 내려보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 그렇게 내려보낸 downstream이 mapError였고, 거기서 AnyError를 PinguError의 elementIsNil로 바꿉니다.&lt;/p&gt;
&lt;p 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_1646589014631&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
2
elementIsNil 에러&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Scan&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Scan입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AGWFR/btrvcGk1eMY/KL8X2ey4bZSrRDdxpIjQK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AGWFR/btrvcGk1eMY/KL8X2ey4bZSrRDdxpIjQK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AGWFR/btrvcGk1eMY/KL8X2ey4bZSrRDdxpIjQK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAGWFR%2FbtrvcGk1eMY%2FKL8X2ey4bZSrRDdxpIjQK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;226&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 upstream publisher의 element를 closure에서 반환한 마지막 값과 함께 내보내는 것이라고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scan의 구현을 보면 아래와 같은데요,&lt;/p&gt;
&lt;pre id=&quot;code_1650288820081&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct Scan&amp;lt;Upstream, Output&amp;gt; : Publisher where Upstream : Publisher {
    public typealias Failure = Upstream.Failure

    public let upstream: Upstream

    public let initialResult: Output

    public let nextPartialResult: (Output, Upstream.Output) -&amp;gt; Output

    public init(upstream: Upstream, initialResult: Output, nextPartialResult: @escaping (Output, Upstream.Output) -&amp;gt; Output)
	public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, S : Subscriber, Upstream.Failure == S.Failure
}&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;생성자를 보면 initialResult값(Output과 같은 타입)과 nextPartialResult값(클로저)이 필요하다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;scan(_:_:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8H0Lv/btrzI1kAdwR/3zk10VN8dJKNoy0yHfJu20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8H0Lv/btrzI1kAdwR/3zk10VN8dJKNoy0yHfJu20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8H0Lv/btrzI1kAdwR/3zk10VN8dJKNoy0yHfJu20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8H0Lv%2FbtrzI1kAdwR%2F3zk10VN8dJKNoy0yHfJu20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;285&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scan Publisher을 활용해서 만든 scan(_:_:)은 Upstream에서 받은 값을 클로저에서 반환한 마지막 값과 함께 내보낸다고 되어있습니다.&amp;nbsp;뭔가 글로 보면 이해가 어려운데, 예제와 실행 결과를 보니 이해가 아주 쉽게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 사용해보면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646590941577&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let publisher = [1, 2, 3, 4, 5].publisher

publisher
    .scan(0) { (latest, current) -&amp;gt; Int in
        print(&quot;latest: \(latest), current: \(current)&quot;)
        return latest + current
    }
    .sink(receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 다음과 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1646590973427&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;latest: 0, current: 1
latest: 1, current: 2
latest: 3, current: 3
latest: 6, current: 4
latest: 10, current: 5
1
3
6
10
15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 최근에 내보낸 값과 현재 값을 클로저에서 사용해서 downstream으로 내려보내는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 가장 최근에 내보낸 값과 현재 값을 더해서 downstream으로 내려보내고 있는데, 그러다 보니 마지막 결과가 15로 publisher의 모든 값을 더한 값과 같아진 것을 볼 수 있네요.&lt;/p&gt;
&lt;h1&gt;TryScan&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryMap과 비슷하게 TryScan도 Scan이랑 똑같으면서 에러를 내려보낼 수 있는 Publisher 같이 보이네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6pdvf/btrvaIDDH5J/emzWkP4IMbknH9xUx7djd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6pdvf/btrvaIDDH5J/emzWkP4IMbknH9xUx7djd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6pdvf/btrvaIDDH5J/emzWkP4IMbknH9xUx7djd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6pdvf%2FbtrvaIDDH5J%2FemzWkP4IMbknH9xUx7djd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;228&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;228&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;실제 구현을 봐도 nextPartialResult에 throws 키워드만 붙어있는 차이만 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646591217241&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct TryScan&amp;lt;Upstream, Output&amp;gt; : Publisher where Upstream : Publisher {
    public typealias Failure = Error

    public let upstream: Upstream

    public let initialResult: Output

    public let nextPartialResult: (Output, Upstream.Output) throws -&amp;gt; Output
	
    public init(upstream: Upstream, initialResult: Output, nextPartialResult: @escaping (Output, Upstream.Output) throws -&amp;gt; Output)

    public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, S : Subscriber, S.Failure == Publishers.TryScan&amp;lt;Upstream, Output&amp;gt;.Failure
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tryScan(_:_:)&lt;/h3&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;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deDJW8/btrzKR2A9um/6WjgjJkfvZq0koxHVRZDnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deDJW8/btrzKR2A9um/6WjgjJkfvZq0koxHVRZDnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deDJW8/btrzKR2A9um/6WjgjJkfvZq0koxHVRZDnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeDJW8%2FbtrzKR2A9um%2F6WjgjJkfvZq0koxHVRZDnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;310&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TryScan Publisher를 활용해서 만든 Operator는 tryScan(_:_:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 아까 사용해본 scan(_:_:)에서 에러만 던질 수 있는 차이점밖에 없네요.&lt;/p&gt;
&lt;p 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_1646591490438&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case evenNumber
}

func checkEvenNumberAndSum(_ latest: Int, _ current: Int) throws -&amp;gt; Int {
    guard current % 2 != 0 else { throw PinguError.evenNumber }
    return latest + current
}

let publisher = [1, 2, 3, 4, 5].publisher

publisher
    .tryScan(0) { latest, current in
        try checkEvenNumberAndSum(latest, current)
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) }
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1646591505605&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
failure(__lldb_expr_62.(unknown context at $103ea62bc).(unknown context at $103ea638c).(unknown context at $103ea6394).PinguError.evenNumber)&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;그러다 보니 publisher의 element 중 첫 번째 값인 1만 더해진 뒤에 에러가 발생해서 publisher가 failure로 끝난 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h1&gt;SetFailureType&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 알아볼 마지막 Publisher는 SetFailureType입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Sjov/btrveMSxp3H/FnDhtPx7X8nMasuKh0GyYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Sjov/btrveMSxp3H/FnDhtPx7X8nMasuKh0GyYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Sjov/btrveMSxp3H/FnDhtPx7X8nMasuKh0GyYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1Sjov%2FbtrveMSxp3H%2FFnDhtPx7X8nMasuKh0GyYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;950&quot; height=&quot;346&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 실패하지 않는 타입의 publisher를 특정 에러 타입을 보낼 수 있는 타입의 &amp;nbsp;publisher로 바꾸는 역할을 한다고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;setFailureType(to:)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D720m/btrzNCJ9U9s/z6JjGGdSGTU9C2Shuh77G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D720m/btrzNCJ9U9s/z6JjGGdSGTU9C2Shuh77G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D720m/btrzNCJ9U9s/z6JjGGdSGTU9C2Shuh77G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD720m%2FbtrzNCJ9U9s%2Fz6JjGGdSGTU9C2Shuh77G1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;279&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SetFailureType Publisher를 활용해서 만든 Operator는 setFailureType(to:)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Upstream의 failure 타입을 바꾸는 역할을 한다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Upstream의 Failure 타입이 Never일 때만 사용할 수 있다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Failure타입이 Never라는 것은 &lt;span&gt;실패하지 않는 타입이라는 건데요,,&lt;/span&gt; &lt;a href=&quot;https://icksw.tistory.com/273?category=937485&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;에서 Just를 사용하면 Failure 타입이 Never인 publisher를 만들 수 있다고 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Just를 사용해서 간단하게 사용해보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cne9Sd/btrvoBQBPrl/skXHeqRyc88fhfvBqks9ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cne9Sd/btrvoBQBPrl/skXHeqRyc88fhfvBqks9ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cne9Sd/btrvoBQBPrl/skXHeqRyc88fhfvBqks9ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcne9Sd%2FbtrvoBQBPrl%2FskXHeqRyc88fhfvBqks9ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1018&quot; height=&quot;338&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 PinguError라는 에러 타입을 하나 만들고, 에러 타입이 Never인 Just로 Publisher를 만든 뒤 setFailureType을 사용해 에러 타입이 PinguError인 Publisher로 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 위와 같이 eraseToAnyPublisher()를 사용했을 때 반환되는 Publisher의 에러 타입이 PinguError인 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 setFailureType으로 에러를 발생할 수 있는 Publisher로 만들어줘야 하는 이유가 뭘까요?&lt;/p&gt;
&lt;pre id=&quot;code_1646665041009&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case pingu
}

let publisher = [1, 2, 3, 4].publisher
let pinguErrorPublisher = PassthroughSubject&amp;lt;Int, PinguError&amp;gt;()

publisher
    .setFailureType(to: PinguError.self) // &amp;lt;Int, PinguError&amp;gt; 타입으로 변경
    .combineLatest(pinguErrorPublisher) // Output, Failure 타입이 같기 때문에 combineLatest 사용 가능!
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) }
    )

pinguErrorPublisher.send(0)

// 실행 결과
(4, 0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 combineLatest와 같이 여러 개의 publisher들을 함께 사용해야 하는 경우가 있습니다. 이런 경우에는 Publisher의 Output, Failure 타입이 같아야 사용이 가능한데요, 이럴 때 Failure 타입을 맞춰주기 위해 setFailureType을 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Mapping Elements로 분류된 Operator들을 살펴봤습니다.&lt;/p&gt;
&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://icksw.tistory.com/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Filtering Elements로 분류된 Operator를 알아보지 않을까 싶네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Operator가 너무 많아서 좀 당황스러웠는데,, 다 정리를 해보도록 하겠습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아! 전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/MappingElementsOperator.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>MAP</category>
      <category>maperror</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>SCAN</category>
      <category>setfailuretype</category>
      <category>subscriber</category>
      <category>Swift</category>
      <category>trymap</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/277</guid>
      <comments>https://icksw.tistory.com/277#entry277comment</comments>
      <pubDate>Tue, 8 Mar 2022 00:06:22 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 문법] Swift 공식 문서 정리 - 21 - Protocols (프로토콜)</title>
      <link>https://icksw.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Swift 공식 문서의 21번째 단원인 Protocols을 읽고 정리한 글을 쓰려고 합니다.&lt;/p&gt;
&lt;h1&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/LanguageGuide/Protocols.html&quot;&gt;Apple Swift 공식 문서 21단원 - Protocol&lt;/a&gt;&lt;/h1&gt;
&lt;h1&gt;Protocol&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 특정 작업이나 기능에 적합한 메서드, 프로퍼티, 요구사항의 청사진을 정의합니다. 그런 뒤 이러한 요구사항의 실제 구현을 위해 Class, Struct, Enum에서 Protocol을 채택할 수 있습니다. 이때 Protocol이 요구하는 사항을 모두 충족하면 해당 타입은 Protocol을 준수한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준수해야 하는 타입의 요구사항을 정의하는 것 외에도 요구사항의 일부를 구현하거나, 준수하는 타입에 추가 기능을 구현하기 위해 Protocol을 확장할 수도 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol Syntax&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol을 정의하는 방법은 Class, Struct, Enum과 매우 비슷합니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol SomeProtocol {
    // protocol definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class, Struct, Enum을 직접 구현할 때 Protocol을 채택하고 싶다면 타입의 이름 뒤에 채택하고 싶은 Protocol의 이름을 쓰면 됩니다. Protocol은 콤마를 사용해서 여러 개를 채택할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;thrift&quot;&gt;&lt;code&gt;struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class의 경우 상속의 문법도 Protocol 채택과 동일한데요, 부모 Class를 갖는 경우에는 부모 Class의 이름을 가장 먼저 쓰고 그 뒤에 Protocol의 이름들을 나열하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Property Requirements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 특정 이름과 타입을 갖는 인스턴스 프로퍼티, 타입 프로퍼티를 사용해서 요구하는 타입을 정의합니다. Protocol은 프로퍼티가 저장 프로퍼티인지 계산 프로퍼티인지는 지정하지 않고 필요한 프로퍼티의 이름과 타입만 지정합니다. Protocol은 각각의 프로퍼티가 gettable, settable 한지도 지정해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol에서 &lt;code&gt;gettable&lt;/code&gt;, &lt;code&gt;settable&lt;/code&gt;인 프로퍼티를 요구할 경우 해당 프로퍼티는 상수 저장 프로퍼티 혹은 읽기 전용 계산 프로퍼티로는 요구사항을 만족시킬 수 없습니다. Protocol이 &lt;code&gt;gettable&lt;/code&gt; 프로퍼티만 요구할 경우에는 모든 종류의 프로퍼티로 요구사항을 만족시킬 수 있고, 이런 프로퍼티는 &lt;code&gt;settable&lt;/code&gt;에도 유효합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티의 요구사항은 항상 &lt;code&gt;var&lt;/code&gt; 키워드와 함께 선언됩니다. &lt;code&gt;gettable&lt;/code&gt;, &lt;code&gt;settable&lt;/code&gt; 프로퍼티는 타입 선언 뒤에 &lt;code&gt;{ get set }&lt;/code&gt;으로 작성해서 나타내며 &lt;code&gt;gattable&lt;/code&gt; 프로퍼티는 &lt;code&gt;{ get }&lt;/code&gt;으로 작성하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol에서 타입 프로퍼티를 정의할 땐 항상 &lt;code&gt;static&lt;/code&gt; 키워드를 사용해야 합니다. 이러한 규칙은 Class에 Protocol을 채택한 경우에 &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;static&lt;/code&gt; 키워드를 쓰는 경우에도 적용됩니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 하나의 프로퍼티를 요구하는 protocol의 예제입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol FullyNamed {
    var fullName: String { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FullyNamed&lt;/code&gt; protocol은 풀네임을 제공하기 위해 준수해야 하는 타입을 요구합니다. Protocol은 다른 준수하는 타입의 특성에 대한 다른 것을 지정하지는 않습니다. Protocol은 타입에게 &lt;code&gt;fullName&lt;/code&gt;을 제공할 수 있어야 한다는 것만 정해줍니다. Protocol을 준수하는 모든 &lt;code&gt;FullyNamed&lt;/code&gt; 타입에는 String 타입의 &lt;code&gt;fullName&lt;/code&gt;이라는 &lt;code&gt;gettable&lt;/code&gt; 인스턴스 프로퍼티가 존재해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;FullyNamed&lt;/code&gt; protocol을 채택하고 준수하는 Struct에 대한 예입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: &quot;John Appleseed&quot;)
// john.fullName is &quot;John Appleseed&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예는 특정한 이름을 갖는 사람을 나타내는 &lt;code&gt;Person&lt;/code&gt; struct를 정의합니다. 첫 줄에 보면 &lt;code&gt;FullNamed&lt;/code&gt; protocol을 채택한 것도 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Person&lt;/code&gt;의 각 인스턴스에는 String 타입의 &lt;code&gt;fullName&lt;/code&gt;이라는 단일 저장 프로퍼티가 있습니다. 이는 &lt;code&gt;FullyNamed&lt;/code&gt; protocol의 요구 사항을 만족시키기 때문에 &lt;code&gt;Person&lt;/code&gt;이 &lt;code&gt;FullyNamed&lt;/code&gt; protocol을 올바르게 준수했음을 의미합니다. (Swift는 protocol의 요구사항을 만족시키지 않으면 컴파일 타임에 오류를 발생시킵니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;FullyNamed&lt;/code&gt; protocol을 채택하고 준수하는 좀 더 복잡한 class 예제를 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
        return (prefix != nil ? prefix! + &quot; &quot; : &quot;&quot;) + name
    }
}
var ncc1701 = Starship(name: &quot;Enterprise&quot;, prefix: &quot;USS&quot;)
// ncc1701.fullName is &quot;USS Enterprise&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제의 &lt;code&gt;Starship&lt;/code&gt; class는 &lt;code&gt;FullyName&lt;/code&gt; protocol의 요구사항인 &lt;code&gt;fullName&lt;/code&gt; 프로퍼티를 계산 프로퍼티로 구현했습니다. &lt;code&gt;Starship&lt;/code&gt; class의 인스턴스는 &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;prefix&lt;/code&gt;를 저장 프로퍼티로 갖고 있습니다. &lt;code&gt;fullName&lt;/code&gt; 프로퍼티는 &lt;code&gt;prefix&lt;/code&gt;값이 있는 경우 이를 &lt;code&gt;name&lt;/code&gt;의 앞부분에 붙여서 &lt;code&gt;Starship&lt;/code&gt;의 &lt;code&gt;fullName&lt;/code&gt;을 만들게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Method Requirements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 자신을 채택한 타입에게 특정 인스턴스 메서드나 타입 메서드를 요구할 수도 있습니다. 이러한 메서드는 일반적인 인스턴스 메서드, 타입 메서드와 같은 방식으로 protocol 내부에 작성되지만 중괄호나 메서드 본문은 없습니다. 일반적인 메서드와 동일한 규칙에 때라 가변 매개변수는 허용되지만, protocol 정의 내에서는 메서드 매개변수에 기본값을 지정할 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항으로 타입 프로퍼티를 정의했을 때와 마찬가지로 타입 메서드를 요구할 때는 항상 &lt;code&gt;static&lt;/code&gt; 키워드를 사용해야 합니다. 이는 protocol이 요구하는 메서드를 class가 정의할 때 &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;static&lt;/code&gt; 키워드를 사용하는 경우에도 동일합니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol SomeProtocol {
    static func comeTypeMethod()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 하나의 인스턴스 메서드를 요구하는 protocol을 정의하는 예입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol RandomNumberGenerator {
    func random() -&amp;gt; Double
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;RandomNumberGenerator&lt;/code&gt; protocol은 자신을 채택한 타입에게 Double 타입을 반환하는 &lt;code&gt;random&lt;/code&gt;이라는 인스턴스 메서드를 정의하라고 요구합니다. Protocol의 일부로 지정하지는 않았지만 이 값은 0.0 ~ 1.0 사이의 숫자를 반환한다고 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;RandomNumberGenerator&lt;/code&gt; Protocol은 각각의 난수가 생성되는 방식에 대한 어떠한 것도 만들지 않았습니다. Generator가 난수를 생성하는 방법만 제공하면 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;code&gt;RandomNumberGenerator&lt;/code&gt; protocol을 채택하고 준수하는 class를 구현해보겠습니다. 이 class는 linear congruential generator로 알려진 pseudorandom number generator 알고리즘을 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -&amp;gt; Double {
        lastRandom = ((lastRandom * a + c)
            .truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
print(&quot;Here's a random number: \(generator.random())&quot;)
// Prints &quot;Here's a random number: 0.3746499199817101&quot;
print(&quot;And another one: \(generator.random())&quot;)
// Prints &quot;And another one: 0.729023776863283&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mutating Method Requirements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드가 인스턴스를 수정해야 하는 경우가 있습니다. 값 타입(struct, enum)에 대한 인스턴스 메서드의 경우 &lt;code&gt;func&lt;/code&gt; 키워드 앞에 &lt;code&gt;mutating&lt;/code&gt; 키워드를 배치해서 해당 메서드가 인스턴스의 값을 수정할 수 있음을 나타냅니다. 여기에 대한 자세한 설명은 &lt;a href=&quot;https://icksw.tistory.com/19&quot;&gt;Modifying Value Types from Within Instance Methods&lt;/a&gt;을 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol을 채택하는 모든 타입의 인스턴스를 변경하기 위한 인스턴스 메서드 요구사항을 정의하는 경우 &lt;code&gt;mutating&lt;/code&gt; 키워드로 메서드에 표시해주면 됩니다. 이를 통해 struct, enum이 protocol이 요구하는 메서드를 인스턴스를 수정할 수 있는 메서드로 만들 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: Protocol이 요구하는 인스턴스 메서드에 &lt;code&gt;mutating&lt;/code&gt;을 표시하더라도 class는 해당 메서드를 구현할 때 &lt;code&gt;mutating&lt;/code&gt; 키워드를 사용할 필요가 없습니다. &lt;code&gt;mutating&lt;/code&gt;은 struct, enum의 경우에만 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 하나의 인스턴스 메서드를 요구하는 &lt;code&gt;Togglable&lt;/code&gt; protocol입니다. 이름에서 알 수 있듯 &lt;code&gt;toggle()&lt;/code&gt; 메서드는 일반적으로 해당 타입의 프로퍼티를 수정해서 해당 타입의 상태를 전환하기 위한 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;toggle()&lt;/code&gt;메서드는 &lt;code&gt;Togglable&lt;/code&gt; protocol 정의의 일부로 &lt;code&gt;mutating&lt;/code&gt; 키워드로 표시되어 메서드가 호출될 때 해당 인스턴스의 상태를 변경할 수 있다는 것을 알려줍니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol Togglable {
    mutating func toggle()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Struct, Enum이 &lt;code&gt;Togglable&lt;/code&gt; protocol을 채택하는 경우 &lt;code&gt;mutating&lt;/code&gt; 키워드를 사용해서 &lt;code&gt;toggle()&lt;/code&gt; 메서드를 구현하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제는 &lt;code&gt;OnOffSwitch&lt;/code&gt;라는 Enum을 정의한 코드입니다. &lt;code&gt;OnOffSwitch&lt;/code&gt;는 &lt;code&gt;on&lt;/code&gt;, &lt;code&gt;off&lt;/code&gt;로 표시되는 두 상태를 &lt;code&gt;toggle&lt;/code&gt;합니다. Enum의 &lt;code&gt;toggle()&lt;/code&gt; 구현에는 &lt;code&gt;mutating&lt;/code&gt; 키워드를 사용해서 &lt;code&gt;Toggleable&lt;/code&gt; protocol의 요구사항과 일치하도록 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch is now equal to .on&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Initilaizer Requirements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 자신을 채택한 타입에 특정 생성자를 요구할 수도 있습니다. 이러한 생성자는 일반 생성자와 정확히 같은 방식으로 protocol 정의의 일부로 만들지만 중괄호나 생성자 본문은 작성하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol SomeProtocol {
    init(someParameter: Int)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Class Implementations of Protocol Initializer Requirements&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;designated 생성자, convenience 생성자로 생성자를 요구하는 protocol을 채택한 class를 구현할 수 있습니다. 두 경우 모두 생성자에 &lt;code&gt;required&lt;/code&gt;를 표시해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;required&lt;/code&gt; 생성자를 사용하면 class의 모든 서브 클래스도 해당 생성자를 명시적으로 혹은 상속된 구현을 통해 구현하므로, 부모 class가 채택 중인 protocol을 준수하도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;required&lt;/code&gt; 생성자에 대한 자세한 정보는 &lt;a href=&quot;https://icksw.tistory.com/49&quot;&gt;Required Initializers&lt;/a&gt;을 참고해주세요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: &lt;code&gt;final&lt;/code&gt; 클래스는 서브 클래싱 할 수 없기 때문에 &lt;code&gt;final&lt;/code&gt; 클래스는 protocol이 요구하는 생성자에 &lt;code&gt;required&lt;/code&gt;를 표시할 필요가 없습니다. &lt;code&gt;final&lt;/code&gt;에 대한 자세한 정보는 &lt;a href=&quot;https://icksw.tistory.com/24&quot;&gt;Preventing Overrides&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브클래스가 슈퍼클래스의 designated 생성자를 override 하고 protocol에서 요구하는 생성자도 구현하는 경우 &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;override&lt;/code&gt; 수정자를 모두 사용해서 생성자를 구현합니다. 즉 슈퍼클래스는 protocol을 채택하지 않았는데, 서브클래스에서는 protocol을 채택한 경우는 아래와 같이 구현하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // &quot;required&quot; from SomeProtocol conformance; &quot;override&quot; from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Failable Initializer Requirements&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 &lt;a href=&quot;https://icksw.tistory.com/49&quot;&gt;Failable Initializers&lt;/a&gt;에 정의된 대로 실패 가능한 생성자를 요구사항으로 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol의 실패 가능한 생성자 요구사항은 자신을 채택한 타입이 실패 가능, 실패 불가능한 생성자가 있을 때 만족됩니다. 실패할 수 없는 생성자를 요구하는 protocol을 채택한 경우, 실패할 수 없는 생성자 또는 암시적으로 래핑 되지 않은 실패 가능한 생성자로 만족시킬 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol as Types&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 자체적으로 기능을 구현하지 않습니다. 하지만 코딩을 할 때 Protocol은 하나의 타입처럼 사용할 수 있습니다. Protocol을 타입처럼 사용하는 것을 &lt;code&gt;existential type&lt;/code&gt;이라고 하는 경우가 있는데 이는 &quot;T가 protocol을 준수하도록 타입 T가 존재합니다.&quot;라는 문구에서 유래되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음을 포함해서 다른 타입이 허용되는 여러 위치에서 Protocol을 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수, 메서드, 생성자의 매개변수 타입 또는 반환 타입&lt;/li&gt;
&lt;li&gt;상수, 변수, 프로퍼티의 타입&lt;/li&gt;
&lt;li&gt;배열, 딕셔너리 등의 항목 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: Protocol이 타입이므로 (앞서 봤던 &lt;code&gt;FullyNamed&lt;/code&gt;, &lt;code&gt;RandomNumberGenerator&lt;/code&gt;와 같이..) Swift의 다른 타입들(예를 들어 Int, String, Double)처럼 이름이 대문자로 시작합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 protocol을 타입처럼 사용한 예입니다.&lt;/p&gt;
&lt;pre class=&quot;zephir&quot;&gt;&lt;code&gt;class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -&amp;gt; Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예는 보드게임에서 사용할 n면체 주사위를 나타내는 &lt;code&gt;Dice&lt;/code&gt;라는 새로운 class를 정의한 코드입니다. &lt;code&gt;Dice&lt;/code&gt;인스턴스에는 면의 수를 나타내는 Int 타입인 &lt;code&gt;sides&lt;/code&gt; 프로퍼티와, 주사위를 굴린 값을 생성하는 &lt;code&gt;RandomNumberGenerator&lt;/code&gt;타입의 &lt;code&gt;generator&lt;/code&gt; 프로퍼티가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;generator&lt;/code&gt; 프로퍼티는 &lt;code&gt;RandomNumberGenerator&lt;/code&gt; 타입입니다. 따라서 &lt;code&gt;RandomNumberGenerator&lt;/code&gt; protocol을 채택하는 모든 타입의 인스턴스를 사용할 수 있습니다. 인스턴스가 &lt;code&gt;RandomNumberGenerator&lt;/code&gt; protocol을 채택한다는 점을 제외하고 이 속성에 할당될 인스턴스에게 요구하는 것은 없습니다. &lt;code&gt;generator&lt;/code&gt;의 타입이 &lt;code&gt;RandomNumberGenerator&lt;/code&gt; 이므로 &lt;code&gt;Dice&lt;/code&gt; class의 내부 코드는 이 protocol이 준수하는 모든 인스턴스에 적용되는 방식으로만 &lt;code&gt;generator&lt;/code&gt;를 사용할 수 있습니다. 즉 &lt;code&gt;generator&lt;/code&gt;의 기본 타입에 정의된 메서드나 프로퍼티를 사용할 수 없습니다. 하지만 &lt;a href=&quot;https://icksw.tistory.com/57&quot;&gt;DownCasting&lt;/a&gt;에서 설명한 슈퍼클래스를 서브클래스로 다운 캐스트 하는 것처럼, protocol 타입을 기본 타입으로 다운 캐스트 해서 사용할 수는 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Dice&lt;/code&gt;는 초기 상태를 설정하기 위한 생성자도 있습니다. 이 생성자에는 &lt;code&gt;RandomNumberGenerator&lt;/code&gt;타입의 &lt;code&gt;generator&lt;/code&gt;라는 매개변수가 있습니다. 새로운 &lt;code&gt;Dice&lt;/code&gt; 인스턴스를 생성할 때 &lt;code&gt;generator&lt;/code&gt; 매개변수에는 &lt;code&gt;RandomNumberGenerator&lt;/code&gt;를 채택한 모든 인스턴스 값을 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Dice&lt;/code&gt;는 1과 &lt;code&gt;sided&lt;/code&gt; 사이의 정수 값을 반환하는 인스턴스 메서드인 &lt;code&gt;roll&lt;/code&gt;을 제공합니다. 이 메서드는 &lt;code&gt;generator&lt;/code&gt;의 &lt;code&gt;random()&lt;/code&gt;메서드를 호출해서 0.0 ~ 1.0 사이의 새로운 난수를 만들고 이 난수를 사용해서 범위 내의 값을 만듭니다. &lt;code&gt;generator&lt;/code&gt;는 &lt;code&gt;RandomNumberGenerator&lt;/code&gt;를 채택했기 때문에 &lt;code&gt;random()&lt;/code&gt; 메서드가 반드시 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Dice&lt;/code&gt; class를 사용해서 &lt;code&gt;LinearCongruentialGenerator&lt;/code&gt; 인스턴스를 &lt;code&gt;generator&lt;/code&gt;로 사용해서 6면 주사위를 만드는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
    print(&quot;Random dice roll is \(d6.roll())&quot;)
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Delegation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Delegation&lt;/code&gt;은 class나 struct가 일부 책임을 다른 타입의 인스턴스에 넘길 수 있도록 하는 디자인 패턴입니다. Delegation 패턴은 위임된 책임을 캡슐화하는 protocol을 정의하여 구현됩니다. 따라서 protocol을 채택한 타입이 위임된 기능을 제공하도록 보장됩니다. Delegation을 사용하여 특정 작업에 응답하거나, 해당 소스의 기본 타입을 알 필요 없이 외부 소스에서 데이터를 검색할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예는 주사위 기반 보드게임에 사용하기 위한 2개의 protocol을 정의한 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol DiceGame {
    var dice: Dice { get }
    func play()
}
protocol DiceGameDelegate: AnyObject {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DiceGame&lt;/code&gt; 프로토콜은 주사위와 관련된 모든 게임에서 채택할 수 있는 Protocol입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DiceGameDelegate&lt;/code&gt; protocol을 채택해서 &lt;code&gt;DiceGame&lt;/code&gt;의 진행 상황을 추적할 수 있습니다. 강한 참조 사이클을 방지하기 위해 delegate는 &lt;code&gt;weak&lt;/code&gt; 참조로 선언됩니다. &lt;code&gt;weak&lt;/code&gt; 참조에 대한 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/80&quot;&gt;Strong Reference Cycles BetweenClass Instances&lt;/a&gt;를 참고해주세요! 잠시 뒤에 나오는 &lt;code&gt;Class-Only Protocols&lt;/code&gt; 섹션에서 나올 내용을 미리 말씀드리면, class 전용 protocol은 &lt;code&gt;AnyObject&lt;/code&gt;를 상속하는 것으로 표시하면 됩니다. Protocol을 class 전용으로 표시하면 이번 챕터의 뒷부분에 나오는 &lt;code&gt;SnakesAndLadders&lt;/code&gt; class의 delegate처럼 &lt;code&gt;weak&lt;/code&gt;참조로 선언할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 원래 &lt;a href=&quot;https://icksw.tistory.com/7&quot;&gt;Control Flow&lt;/a&gt;에서 소개된 &lt;code&gt;Snake and Ladders&lt;/code&gt; 게임 버전입니다. 이 버전은 &lt;code&gt;DiceGame&lt;/code&gt; protocol 채택하고 &lt;code&gt;DiceGameDelegate&lt;/code&gt;에게 진행상황을 알리기 위해 dice-roll에 &lt;code&gt;Dice&lt;/code&gt; 인스턴스를 사용하도록 설정되었습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = Array(repeating: 0, count: finalSquare + 1)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    weak var delegate: DiceGameDelegate?
    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare &amp;gt; finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Snakes and Ladders&lt;/code&gt;의 게임 설명을 보고 싶으시면 &lt;a href=&quot;https://icksw.tistory.com/7&quot;&gt;Control Flow - Break&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 게임 버전은 &lt;code&gt;DiceGame&lt;/code&gt; protocol을 채택한 &lt;code&gt;SnakesAndLadders&lt;/code&gt;라는 class로 래핑 됩니다. Protocol을 준수하기 위해 &lt;code&gt;gattable dice&lt;/code&gt; 프로퍼티와 &lt;code&gt;play()&lt;/code&gt; 메서드를 제공합니다. (&lt;code&gt;dice&lt;/code&gt; 프로퍼티는 생성 후 변경할 필요가 없기 때문에 &lt;code&gt;let&lt;/code&gt;으로 선언되며 protocol은 &lt;code&gt;gettable&lt;/code&gt;만 요구하므로 문제가 없습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Snakes and Ladders&lt;/code&gt; 게임 보드 설정은 class의 &lt;code&gt;init()&lt;/code&gt; 생성자에서 이뤄집니다. 모든 게임 논리는 Protocol의 필수 요소인 &lt;code&gt;dice&lt;/code&gt; 프로퍼티를 사용해서 주사위 역할을 제공하는 protocol의 &lt;code&gt;play()&lt;/code&gt; 메서드로 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;delegate&lt;/code&gt; 프로퍼티는 옵셔널 &lt;code&gt;DiceGameDelegate&lt;/code&gt; 타입으로 정의되어있습니다. 게임을 플레이하기 위해 &lt;code&gt;delegate&lt;/code&gt;는 필요하지 않기 때문이죠. 옵셔널 타입이기 때문에 &lt;code&gt;delegate&lt;/code&gt;프로퍼티의 초기값은 nil입니다. 그런 뒤 게임을 생성한 곳에서 적절한 &lt;code&gt;delegate&lt;/code&gt;를 설정할 수 있습니다. &lt;code&gt;DiceGameDelegate&lt;/code&gt; protocol은 class 전용이므로 참조 주기를 방지하기 위해 &lt;code&gt;weak&lt;/code&gt;으로 선언할 수 있는 것도 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DiceGameDelegate&lt;/code&gt;는 게임 진행상황을 추적하는 세 가지 메서드를 제공합니다. 이러한 세 가지 메서드는 위의 &lt;code&gt;play()&lt;/code&gt; 메서드 내에서 게임 논리에 통합되어 새로운 게임이 시작되거나 새 차례가 오거나 게임이 종료될 때까지 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;delegate&lt;/code&gt; 프로퍼티는 옵셔널 &lt;code&gt;DiceGameDelegate&lt;/code&gt;이므로 &lt;code&gt;play()&lt;/code&gt; 메서드는 &lt;code&gt;delegate&lt;/code&gt;에서 메서드를 호출할 때마다 옵셔널 체이닝을 사용합니다. &lt;code&gt;delegate&lt;/code&gt; 프로퍼티가 nil이라면 이러한 호출은 정상적으로 실패하게 되고, nil이 아니라면 &lt;code&gt;delegate&lt;/code&gt; 메서드가 호출되고 &lt;code&gt;SnakesAndLadders&lt;/code&gt; 인스턴스가 매개변수로 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예는 &lt;code&gt;DiceGameDelegate&lt;/code&gt; protocol을 채택한 &lt;code&gt;DiceGameTracker&lt;/code&gt;라는 class를 정의한 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(_ game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders {
            print(&quot;Started a new game of Snakes and Ladders&quot;)
        }
        print(&quot;The game is using a \(game.dice.sides)-sided dice&quot;)
    }
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        numberOfTurns += 1
        print(&quot;Rolled a \(diceRoll)&quot;)
    }
    func gameDidEnd(_ game: DiceGame) {
        print(&quot;The game lasted for \(numberOfTurns) turns&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DiceGameTracker&lt;/code&gt;는 &lt;code&gt;DiceGameDelegate&lt;/code&gt;에 필요한 세 가지 메서드를 모두 구현해뒀습니다. 이런 메서드를 사용해서 게임에서 수행한 차례의 횟수를 알아냅니다. 게임이 시작되면 &lt;code&gt;numberOfTurns&lt;/code&gt; 프로퍼티를 0으로 재설정하고 새로운 턴이 시작될 때마다 값을 증가시키며, 게임이 끝나면 총 턴 수를 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에 있는 &lt;code&gt;gameDidStart(_:)&lt;/code&gt;의 구현은 &lt;code&gt;game&lt;/code&gt; 매개변수를 사용하여 플레이하려는 게임에 대한 이부 소개정보를 출력합니다. &lt;code&gt;game&lt;/code&gt; 매개변수는 &lt;code&gt;SnakesAndLadders&lt;/code&gt;가 아닌 &lt;code&gt;DiceGame&lt;/code&gt; 타입이므로 &lt;code&gt;gameDidStart(_:)&lt;/code&gt;는 &lt;code&gt;DiceGame&lt;/code&gt; 프로토콜의 일부로 구현된 메서드와 프로퍼티에만 접근하고 사용할 수 있습니다. 그러나 메서드는 여전히 타입 캐스팅을 사용하여 기본 인스턴스 형식을 쿼리 할 수도 있습니다. 이 예제에서는 게임이 실제로 백 단에서 &lt;code&gt;SnakesAndLadders&lt;/code&gt;의 인스턴스인지 확인하고 그렇다면 적절한 메시지를 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;gameDidStart(_:)&lt;/code&gt; 메서드는 전달된 &lt;code&gt;game&lt;/code&gt; 매개변수의 &lt;code&gt;dice&lt;/code&gt; 프로퍼티에도 접근합니다. &lt;code&gt;game&lt;/code&gt;은 &lt;code&gt;DiceGame&lt;/code&gt; protocol을 준수하므로 &lt;code&gt;dice&lt;/code&gt; 프로퍼티가 반드시 존재하기 때문에 &lt;code&gt;gameDidStart(_:)&lt;/code&gt; 메서드는 어떤 종류의 게임이 플레이되는지 상관없이 &lt;code&gt;dice&lt;/code&gt;의 &lt;code&gt;side&lt;/code&gt; 프로퍼티에 접근하여 출력할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DiceGameTracker&lt;/code&gt;가 작동하는 모습은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Adding Protocol Conformance with an Extension&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 타입의 소스 코드에서는 접근할 수 없는 경우에, 기존 타입의 extension을 통해 새로운 protocol을 채택할 수도 있습니다. &lt;code&gt;Extension&lt;/code&gt;은 기존 타입에 새로운 프로퍼티, 메서드, 서브 스크립트를 추가할 수 있으므로 프로토콜이 요구하는 것도 만족시킬 수 있습니다. &lt;code&gt;Extension&lt;/code&gt;에 대한 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/59&quot;&gt;Extension&lt;/a&gt;을 참고해주세요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: 타입의 기존 인스턴스에도 해당 타입의 extension에 의해 protocol이 채택되면 자동으로 반영됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;code&gt;TextRepresentable&lt;/code&gt;이라는 텍스트로 표현되는 방법이 있는 모든 타입을 구현할 수 있는 protocol입니다. 이는 자신에 대한 설명 혹은 현재 상태에 대한 텍스트 일 수 있겠죠.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol TextRepresentable {
    var textualDescription: String { get }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 본 &lt;code&gt;Dice&lt;/code&gt; 클래스도 extension을 사용하여 &lt;code&gt;TextRepresentable&lt;/code&gt;를 채택하고 준수하도록 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;extension Dice: TextRepresentable {
    var textualDescription: String {
        return &quot;A \(sides)-sided dice&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 extension은 &lt;code&gt;Dice&lt;/code&gt;가 원래 구현에서 제공하는 것과 동일한 방법으로 새로운 protocol을 채택합니다. Protocol 이름은 타입 이름 뒤에 콜론을 사용하여 작성하면 되고, Protocol의 모든 요구사항은 해당 extension의 중괄호 안에서 정의하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 &lt;code&gt;Dice&lt;/code&gt; 인스턴스를 &lt;code&gt;TextRepresentable&lt;/code&gt;로도 처리할 수 있게 되었습니다!&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// Prints &quot;A 12-sided dice&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 &lt;code&gt;SnakesAndLadders&lt;/code&gt; class도 &lt;code&gt;TextRepresentable&lt;/code&gt; protocol을 채택하고 준수하게 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return &quot;A game of Snakes and Ladders with \(finalSquare) squares&quot;
    }
}
print(game.textualDescription)
// Prints &quot;A game of Snakes and Ladders with 25 squares&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Conditionally Conforming to a Protocol&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 타입은 제네릭 매개변수가 protocol을 준수하는 경우와 같은 특정 조건에서만 protocol의 요구사항을 만족시킬 수 있습니다. 타입에 extension을 사용할 때 제약조건을 나열하여 제네릭 타입이 조건부로 protocol을 준수하도록 만들 수 있습니다. &lt;code&gt;where&lt;/code&gt;을 사용하여 채택하려는 protocol 이름 뒤에 제약조건을 작성하면 되는데요, &lt;code&gt;where&lt;/code&gt; 절에 대한 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/69&quot;&gt;Generic Where Clauses&lt;/a&gt;를 참고하세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 Array 인스턴스가 &lt;code&gt;TextRepresentable&lt;/code&gt;을 채택한 타입의 요소를 저장할 때만 &lt;code&gt;TextRepresentable&lt;/code&gt; protocol의 요구사항을 준수하도록 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return &quot;[&quot; + itemsAsText.joined(separator: &quot;, &quot;) + &quot;]&quot;
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// Prints &quot;[A 6-sided dice, A 12-sided dice]&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Declaring Protocol Adoption with an Extension&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입이 protocol의 모든 요구 사항을 이미 준수하지만 해당 protocol을 채택한다고 명시하지는 않은 경우 빈 확장자를 사용하여 protocol을 채택하도록 할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;struct Hamster {
    var name: String
    var textualDescription: String {
        return &quot;A hamster named \(name)&quot;
    }
}
extension Hamster: TextRepresentable {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Hamster&lt;/code&gt; 인스턴스는 &lt;code&gt;TextRepresentable&lt;/code&gt;이 필수 타입인 모든 곳에서 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;let simonTheHamster = Hamster(name: &quot;Simon&quot;)
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// Prints &quot;A hamster named Simon&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: 타입이 요구 사항을 충족한다고 해서 protocol을 자동으로 채택하지는 않습니다. 언제나 protocol은 명시적으로 선언해야 채택할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Adopting a Protocol Using a Synthesized Implementation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 많은 간단한 경우에 &lt;code&gt;Equtable&lt;/code&gt;, &lt;code&gt;Hashable&lt;/code&gt;, &lt;code&gt;Comparable&lt;/code&gt;에 대한 protocol의 요구사항을 자동으로 준수할 수 있습니다. 이러한 synthesized implementation(합성 구현)을 사용하면 protocol의 요구 사항을 직접 구현하기 위해 반복적인 코드를 작성할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 다음 종류의 custom 타입에 대해 &lt;code&gt;Equatable&lt;/code&gt; synthesized implementation을 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Equatable&lt;/code&gt; protocol을 준수하는 저장 프로퍼티만 있는 struct&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Equatable&lt;/code&gt; protocol을 준수하는 associated type만 있는 enum&lt;/li&gt;
&lt;li&gt;associated type이 없는 enum&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;==&lt;/code&gt;의 synthesized implementation을 사용하려면 &lt;code&gt;==&lt;/code&gt; 연산자를 직접 구현하지 말고 코드에 &lt;code&gt;Equatable&lt;/code&gt; protocol을 채택합니다. &lt;code&gt;Equatable&lt;/code&gt; protocol은 &lt;code&gt;!=&lt;/code&gt;의 기본 구현을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 Vector2D 구조와 유사한 3차원 위치 벡터 (x, y, z)에 대한 Vector3D struct를 정의한 코드입니다. x, y, z 프로퍼티 모두 &lt;code&gt;Equatable&lt;/code&gt; 타입이므로 Vector3D는 &lt;code&gt;==&lt;/code&gt; 연산자의 synthesized implementation을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;struct Vector3D: Equatable {
    var x = 0.0, y = 0.0, z = 0.0
}

let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
    print(&quot;These two vectors are also equivalent.&quot;)
}
// Prints &quot;These two vectors are also equivalent.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 다음 종류의 custom type에 대해 &lt;code&gt;Hashable&lt;/code&gt;의 synthesized implementation을 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Hashable&lt;/code&gt; protocol을 준수하는 저장 프로퍼티만 있는 struct&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Hashable&lt;/code&gt; protocol을 준수하는 associated type만 있는 enum&lt;/li&gt;
&lt;li&gt;associated type이 없는 enum&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;hash(into:)&lt;/code&gt;의 synthesized implementation을 사용하려면 &lt;code&gt;hash(into:)&lt;/code&gt; 메서드를 직접 구현하지 않고 코드에 &lt;code&gt;Hashable&lt;/code&gt; protocol을 채택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 raw Value가 없는 enum에 대해 &lt;code&gt;Comparable&lt;/code&gt;의 synthesized implementation를 제공합니다. Enum에 associated type이 있는 경우 모두 &lt;code&gt;Comparable&lt;/code&gt; protocol을 준수해야 합니다. &lt;code&gt;&amp;lt;&lt;/code&gt;의 synthesized implementation을 수신하려면 &lt;code&gt;&amp;lt;&lt;/code&gt; 연산자를 직접 구현하지 않고 원래 enum이 선언된 코드에 &lt;code&gt;Comparable&lt;/code&gt; protocol을 채택합니다. &lt;code&gt;Comparable&lt;/code&gt; protocol의 기본 구현인 &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;는 나머지 비교 연산자를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 beginner, intermediate, expert 케이스를 갖는 &lt;code&gt;SkillLevel&lt;/code&gt; enum을 정의한 코드입니다. &lt;code&gt;expert&lt;/code&gt;는 보유한 별의 수에 따라 추가로 순위가 매겨집니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum SkillLevel: Comparable {
    case beginner
    case intermediate
    case expert(stars: Int)
}
var levels = [SkillLevel.intermediate, SkillLevel.beginner,
              SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
for level in levels.sorted() {
    print(level)
}
// Prints &quot;beginner&quot;
// Prints &quot;intermediate&quot;
// Prints &quot;expert(stars: 3)&quot;
// Prints &quot;expert(stars: 5)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Collections of Protocol Types&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 아까 &lt;code&gt;Protocols as Types&lt;/code&gt; 섹션에서 알아본 대로 &lt;code&gt;Array&lt;/code&gt;, &lt;code&gt;Dictionary&lt;/code&gt;와 같은 컬렉션에 저장할 수도 있습니다. 다음 코드는 &lt;code&gt;TextRepresentable&lt;/code&gt;을 저장할 &lt;code&gt;Array&lt;/code&gt;를 만드는 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;inform7&quot;&gt;&lt;code&gt;let things: [TextRepresentable] = [game, d12, simonTheHamster]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Array&lt;/code&gt;의 항목을 for문을 통해 처리하면 각 항목에 대한 &lt;code&gt;textualDesciption&lt;/code&gt;을 출력할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;for thing in things {
    print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;things&lt;/code&gt;는 &lt;code&gt;Dice&lt;/code&gt;, &lt;code&gt;DiceGame&lt;/code&gt;, &lt;code&gt;Hamster&lt;/code&gt;타입이 아닌 &lt;code&gt;TextRepresentable&lt;/code&gt; 타입입니다. 실제로는 그런 타입이라도 모두 &lt;code&gt;TextRepresentable&lt;/code&gt; 타입이고 해당 타입에는 &lt;code&gt;textualDesciprion&lt;/code&gt; 프로퍼티가 있으므로 위와 같이 사용이 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol Inheritance&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 하나 이상의 다른 protocol을 상속할 수 있으며 상속된 요구사항에 요구사항을 추가할 수 있습니다. Protocol 상속 문법은 class의 상속과 유사하지만 상속할 여러 protocol을 쉼표로 구분하여 나열할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 위에서 나온 &lt;code&gt;TextRepresentable&lt;/code&gt; protocol을 상속하는 protocol의 예입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 &lt;code&gt;TextRepresntable&lt;/code&gt;을 상속하는 새로운 protocol인 &lt;code&gt;PrettyTextRepresentable&lt;/code&gt;을 정의합니다. &lt;code&gt;PrettyTextRepresentable&lt;/code&gt;을 채택하는 모든 타입은 &lt;code&gt;TextRepresntable&lt;/code&gt;의 요구사항과 &lt;code&gt;PrettyTextRepresntable&lt;/code&gt;의 요구사항을 모두 만족시켜야 합니다. 위 코드에서는 &lt;code&gt;PrettyTextRepresntable&lt;/code&gt;는 String을 반환하는 &lt;code&gt;prettyTextualDescription&lt;/code&gt;이라는 &lt;code&gt;gettable&lt;/code&gt; 프로퍼티를 제공하기 위한 하나의 요구사항을 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SnakesAndLadders&lt;/code&gt; class는 &lt;code&gt;PrettyTextRepresntable&lt;/code&gt;를 채택하고 준수하도록 extension 할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension SnakesAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = textualDescription + &quot;:\n&quot;
        for index in 1...finalSquare {
            switch board[index] {
            case let ladder where ladder &amp;gt; 0:
                output += &quot;▲ &quot;
            case let snake where snake &amp;lt; 0:
                output += &quot;▼ &quot;
            default:
                output += &quot;○ &quot;
            }
        }
        return output
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 &lt;code&gt;PrettyTextRepresntable&lt;/code&gt; protocol을 채택하고 &lt;code&gt;SnakesAndLadders&lt;/code&gt; 타입에 대한 &lt;code&gt;prettyTextualDescription&lt;/code&gt; 프로퍼티의 구현을 제공한다고 명시합니다. &lt;code&gt;PrettyTextRepresntable&lt;/code&gt;은 무엇이든 &lt;code&gt;TextRepresntable&lt;/code&gt; protocol을 채택해야 하며 따라서 &lt;code&gt;prettyTextRepresntable&lt;/code&gt;의 구현은 &lt;code&gt;TextRepresntable&lt;/code&gt; protocol에서 &lt;code&gt;textualDescription&lt;/code&gt; 프로퍼티에 접근하며 시작됩니다. 콜론과 줄 바꿈을 추가하고 이를 예쁜 텍스트 표현의 시작으로 사용합니다. 그런 뒤 &lt;code&gt;board&lt;/code&gt; 배열의 square 값을 통해 도형을 추가합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;square의 값이 0보다 크면 사다리의 밑면이고 ▲로 표시합니다.&lt;/li&gt;
&lt;li&gt;square의 값이 0보다 작으면 뱀의 머리이고 ▼로 표시합니다.&lt;/li&gt;
&lt;li&gt;둘 다 아니면 square의 값은 0이고 ○로 표시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;prettyTextualDescription&lt;/code&gt; 프로퍼티를 사용하여 &lt;code&gt;SnakesAndLadders&lt;/code&gt; 인스턴스의 예쁜 텍스트를 출력할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Class-Only Protocol&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol의 상속 목록에 &lt;code&gt;AnyObject&lt;/code&gt; 프로토콜을 추가해서 Protocol을 class만 채택할 수 있도록 제한할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 &lt;code&gt;SomeClassOnlyProtocol&lt;/code&gt;은 class 타입에서만 채택할 수 있습니다. &lt;code&gt;SomeClassOnlyProtocol&lt;/code&gt;을 채택하려고 하는 struct, enum은 컴파일 오류를 발생시킵니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: 해당 protocol의 요구사항에 의해 이미 정의된 동작이 참조 및 값 타입의 차이에 대한 설명은 &lt;a href=&quot;https://icksw.tistory.com/11&quot;&gt;Structures and Classes&lt;/a&gt;를 참고하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol Composition&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 여러 protocol을 준수하는 타입을 요구하는 것이 유용할 수 있습니다. protocol composition을 사용하여 여러 개의 protocol을 하나의 protocol로 결합할 수 있습니다. protocol composition은 composition에 있는 모든 protocol의 요구사항이 결합된 임시 로컬 protocol을 정의한 것처럼 작동합니다. protocol composition은 새로운 protocol 타입을 정의하지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protocol composition은 SomeProtocol 및 AnotherProtocol 형식입니다. &lt;code&gt;&amp;amp;&lt;/code&gt;로 구분하여 필요한 만큼 protocol을 나열할 수 있습니다. Protocol 목록 외에도 protocol composition에는 필수 슈퍼 클래스를 지정하는 데 사용할 수 있는 하나의 클래스 타입이 포함될 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;Named&lt;/code&gt;, &lt;code&gt;Aged&lt;/code&gt;라는 2개의 protocol을 함수 매개변수에 대한 단일 protocol composition으로 결합하는 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named &amp;amp; Aged) {
    print(&quot;Happy birthday, \(celebrator.name), you're \(celebrator.age)!&quot;)
}
let birthdayPerson = Person(name: &quot;Malcolm&quot;, age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints &quot;Happy birthday, Malcolm, you're 21!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예에서 &lt;code&gt;Named&lt;/code&gt; Protocol에는 &lt;code&gt;name&lt;/code&gt;이라는 &lt;code&gt;gettable&lt;/code&gt; String 프로퍼티를 요구합니다. &lt;code&gt;Aged&lt;/code&gt; protocol에는 &lt;code&gt;age&lt;/code&gt;라는 &lt;code&gt;gettable&lt;/code&gt; Int 프로퍼티를 요구합니다. 두 개의 protocol 모두 &lt;code&gt;Person&lt;/code&gt;이라는 struct에 채택되었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예에서는 &lt;code&gt;wishHappyBirthday(to:)&lt;/code&gt; 함수를 정의합니다. &lt;code&gt;celebrator&lt;/code&gt; 매개변수 타입은 &lt;code&gt;Named &amp;amp; Aged&lt;/code&gt; 이며, 이는 &lt;code&gt;Named&lt;/code&gt;, &lt;code&gt;Aged&lt;/code&gt; protocol을 모두 준수하는 타입을 의미합니다. 두 개의 필수 protocol을 모두 준수한다면 어떤 타입이건 함수의 매개변수로 사용될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 &lt;code&gt;birthdayPerson&lt;/code&gt;이라는 새로운 &lt;code&gt;Person&lt;/code&gt;인스턴스를 만들고 새로운 인스턴스를 &lt;code&gt;wishHappyBirthday(to:)&lt;/code&gt; 함수에 전달합니다. &lt;code&gt;Person&lt;/code&gt;이 두 개의 protocol을 모두 준수하기 때문에 문제없이 &lt;code&gt;wishHappyBirthdat(to:)&lt;/code&gt; 함수는 작동됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 방금 예의 &lt;code&gt;Named&lt;/code&gt; protocol을 &lt;code&gt;Location&lt;/code&gt; class와 결합한 예제입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class Location {
    var latitude: Double
    var longitude: Double
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}
class City: Location, Named {
    var name: String
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
func beginConcert(in location: Location &amp;amp; Named) {
    print(&quot;Hello, \(location.name)!&quot;)
}

let seattle = City(name: &quot;Seattle&quot;, latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints &quot;Hello, Seattle!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;beginConcert(in:)&lt;/code&gt; 함수는 &lt;code&gt;Location &amp;amp; Named&lt;/code&gt; 타입의 매개변수를 사용합니다. 이는 &lt;code&gt;Location&lt;/code&gt;의 서브클래스이고 &lt;code&gt;Named&lt;/code&gt; protocol을 준수하는 모든 타입을 뜻합니다. 위 코드에서 &lt;code&gt;City&lt;/code&gt;는 두 조건을 모두 충족합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Person&lt;/code&gt;이 &lt;code&gt;Location의 하위 클래스가 아니므로&lt;/code&gt;beginConcert(in:)&lt;code&gt;함수에&lt;/code&gt;birthdayPerson&lt;code&gt;을 전달하는 것은 옳지 않습니다. 마찬가지로&lt;/code&gt;Named&lt;code&gt;protocol을 준수하지 않는&lt;/code&gt;Location&lt;code&gt;의 서브 클래스를 만든 경우에도&lt;/code&gt;beginConcert(in:)`에 전달하는 것이 옳지 않습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Checking for Protocol Conformance&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 캐스팅에 설명된 &lt;code&gt;is&lt;/code&gt;, &lt;code&gt;as&lt;/code&gt; 연산자를 사용하여 protocol 적합성을 확인하고 특정 protocol로 캐스팅할 수 있습니다. protocol을 확인하고 캐스팅하는 것은 타입을 확인하고 캐스팅하는 것과 동일한 문법을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;is&lt;/code&gt; 연산자는 인스턴스가 protocol을 준수하면 true를 반환하고 그렇지 않으면 false를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;as?&lt;/code&gt; 연산자는 protocol 타입의 옵셔널 값을 반환하고 인스턴스가 해당 protocol을 준수하지 않으면 nil을 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;as!&lt;/code&gt; 연산자는 다운 캐스트를 protocol 타입으로 강제하고 성공하지 못하면 런타임 에러를 발생시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예는 &lt;code&gt;area&lt;/code&gt;라는 &lt;code&gt;gettable&lt;/code&gt; Dobule 프로퍼티를 요구하는 &lt;code&gt;HasArea&lt;/code&gt; protocol을 정의한 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol HasArea {
    var area: Double { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;code&gt;HasArea&lt;/code&gt; protocol을 준수하는 &lt;code&gt;Circle&lt;/code&gt;, &lt;code&gt;Country&lt;/code&gt; 클래스를 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Circle&lt;/code&gt; class는 계산 프로퍼티로 선언된 &lt;code&gt;area&lt;/code&gt; 프로퍼티를 기반으로 &lt;code&gt;radius&lt;/code&gt;라는 저장 프로퍼티를 구현합니다. 두 개의 class모두 &lt;code&gt;HasArea&lt;/code&gt; protocol을 준수하고 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;HasArea&lt;/code&gt; protocol을 준수하지 않는 &lt;code&gt;Animal&lt;/code&gt;이라는 class입니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Circle&lt;/code&gt;, &lt;code&gt;Country&lt;/code&gt;, &lt;code&gt;Animal&lt;/code&gt; class는 함께 상속하는 class가 없습니다. 하지만 모두 class이므로 &lt;code&gt;AnyObject&lt;/code&gt; 타입을 저장하는 Array에 저장할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Objects&lt;/code&gt; array는 &lt;code&gt;radius&lt;/code&gt;가 2.0인 &lt;code&gt;Circle&lt;/code&gt; 인스턴스, &lt;code&gt;area&lt;/code&gt;가 243610인 &lt;code&gt;Country&lt;/code&gt; 인스턴스, &lt;code&gt;legs&lt;/code&gt;가 4인 &lt;code&gt;Animal&lt;/code&gt; 인스턴스로 초기화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Objects&lt;/code&gt; array를 for문을 사용하여 각각의 element를 검사해서 &lt;code&gt;HasArea&lt;/code&gt; protocol을 준수하는지 확인해봅시다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;for object in objects {
    if let objectWithArea = object as? HasArea {
        print(&quot;Area is \(objectWithArea.area)&quot;)
    } else {
        print(&quot;Something that doesn't have an area&quot;)
    }
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 객체가 &lt;code&gt;HasArea&lt;/code&gt; protocol을 준수할 때마다 &lt;code&gt;as?&lt;/code&gt; 연산자가 반환하는 옵셔널 값은 &lt;code&gt;objectWithArea&lt;/code&gt;라는 상수로 래핑 해제됩니다. &lt;code&gt;objectWithArea&lt;/code&gt; 상수는 &lt;code&gt;HasArea&lt;/code&gt; 타입으로 알려져 있으므로 해당 &lt;code&gt;area&lt;/code&gt; 프로퍼티에 접근하고 타입이 안전한 방식으로 print 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 객체는 캐스팅 프로세스에 의해 변경되지는 않고 계속해서 &lt;code&gt;Circle&lt;/code&gt;, &lt;code&gt;Country&lt;/code&gt;, &lt;code&gt;Animal&lt;/code&gt; 하지만 &lt;code&gt;objectWithArea&lt;/code&gt;에 저장되는 시점에는 &lt;code&gt;HasArea&lt;/code&gt;타입이 되므로 &lt;code&gt;area&lt;/code&gt; 프로퍼티에만 접근할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Optional Protocol Requirements&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 선택적 요구사항을 정의할 수도 있습니다. 이러한 요구사항은 protocol을 채택한 타입이 반드시 구현할 필요가 없습니다. 선택적 요구사항은 protocol 정의의 일부로 &lt;code&gt;optional&lt;/code&gt;이라는 접두사가 붙습니다. Objective-C와 함께 사용되는 코드를 작성할 수 있도록 선택적 요구사항을 사용할 수 있는데요, 이 경우 Protocol과 선택적 요구사항은 모든 &lt;code&gt;@objc&lt;/code&gt; 프로퍼티로 표시되어야 합니다. &lt;code&gt;@objc&lt;/code&gt; 프로토콜은 Objective-C class 또는 &lt;code&gt;@objc&lt;/code&gt; class에서 상속받은 class에서만 채택할 수 있습니다. 따라서 struct, enum에서는 채택될 수 없죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드나 프로퍼티를 선택적 요구 사항으로 선언하면 해당 타입은 자동으로 옵셔널 타입이 됩니다. 예를 들어 &lt;code&gt;(Int) -&amp;gt; String&lt;/code&gt; 타입의 메서드는 &lt;code&gt;((Int) -&amp;gt; String)?&lt;/code&gt; 타입이 됩니다. 전체 함수 타입은 메서드의 반환 값이 아니라 옵셔널로 래핑 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택적 요구사항은 요구사항이 protocol을 준수하는 타입에 의해 구현되지 않았을 수 있기 때문에 옵셔널 체이닝으로 호출할 수 있습니다. &lt;code&gt;someOptionalMethod?(someArgument)&lt;/code&gt;와 같이 호출될 때 메서드 이름 뒤에 물음표를 넣어 옵셔널 메서드의 구현을 확인합니다. 옵셔널 체이닝에 대한 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/52&quot;&gt;Optional Chaining&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예에서는 외부 데이터 소스를 사용하여 &lt;code&gt;increment&lt;/code&gt;를 제공하는 &lt;code&gt;Counter&lt;/code&gt;라는 정수 계산 class를 정의할 겁니다. 이 data source는 두 가지 선택적 요구사항이 있는 &lt;code&gt;CounterDataSource&lt;/code&gt; protocol에 의해 정의됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -&amp;gt; Int
    @objc optional var fixedIncrement: Int { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;CounterDataSource&lt;/code&gt; protocol은 &lt;code&gt;increment(forCount:)&lt;/code&gt;라는 옵셔널 메서드 요구사항과 &lt;code&gt;fixedIncrement&lt;/code&gt;라는 옵셔널 프로퍼티 요구사항을 정의합니다. 이러한 요구사항은 data Source가 &lt;code&gt;Counter&lt;/code&gt; 인스턴스에 적절한 증분량을 제공하는 두 가지 다른 방법을 정의합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: 엄밀히 말하면 protocol 요구사항을 구현하지 않아도 &lt;code&gt;CounterDataSource&lt;/code&gt; protocol을 준수하는 class를 만들 수 있습니다. 요구사항이 모두 옵셔널이기 때문이죠. 물론 굳이 그럴 필요는 없겠죠.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 구현된 &lt;code&gt;Counter&lt;/code&gt; class에는 &lt;code&gt;CounterDataSource?&lt;/code&gt; 타입의 &lt;code&gt;dataSource&lt;/code&gt; 프로퍼티가 있습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Counter&lt;/code&gt; class는 현재 값을 &lt;code&gt;count&lt;/code&gt;라는 변수 프로퍼티에 저장합니다. &lt;code&gt;Counter&lt;/code&gt; class는 또한 메서드가 호출될 때마다 &lt;code&gt;count&lt;/code&gt; 프로퍼티를 증가시키는 &lt;code&gt;increment&lt;/code&gt;라는 메서드를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;increment()&lt;/code&gt; 메서드는 먼저 &lt;code&gt;dataSource&lt;/code&gt;에서 &lt;code&gt;increment(forCount:)&lt;/code&gt; 메서드의 구현을 찾아 증가량을 찾으려고 시도합니다. &lt;code&gt;increment()&lt;/code&gt; 메서드는 옵셔널 체이닝을 사용하여 &lt;code&gt;increment(forCount:)&lt;/code&gt;의 호출을 시도하고 &lt;code&gt;count&lt;/code&gt;값을 메서드의 단일 인수로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 수준의 옵셔널 체이닝이 여기서 작동합니다. 첫 번째로 &lt;code&gt;dataSource&lt;/code&gt;가 nil일 수 있으므로 &lt;code&gt;dataSource&lt;/code&gt;의 이름 뒤에 물음표가 있어 &lt;code&gt;dataSource&lt;/code&gt;가 nil이 아닐 때만 &lt;code&gt;increment(forCount:)&lt;/code&gt;를 호출해야 함을 나타냅니다. 두 번째로 &lt;code&gt;dataSource&lt;/code&gt;가 존재하더라고 옵셔널 요구사항인 &lt;code&gt;increment(forCount:)&lt;/code&gt;가 구현되어있다는 보장이 없습니다. 따라서 &lt;code&gt;increment(forCount:)&lt;/code&gt;가 구현되어있지 않을 가능성도 옵셔널 체이닝에 의해 처리됩니다. &lt;code&gt;increment(forCount:)&lt;/code&gt;에 대한 호출은 &lt;code&gt;increment(forCount:)&lt;/code&gt;가 존재하는 경우에만 발생합니다. 이것이 &lt;code&gt;increment(forCount:)&lt;/code&gt;도 이름 뒤에 물음표가 있는 이유입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 두 가지 이유로 인해 &lt;code&gt;increment(forCount:)&lt;/code&gt; 호출이 실패할 수 있으므로 &lt;code&gt;increment(forCount:)&lt;/code&gt;은 옵셔널 Int값을 반환합니다. &lt;code&gt;increment(forCount:)&lt;/code&gt;가 &lt;code&gt;CounterDataSource&lt;/code&gt;의 정의에서 옵셔널이 아닌 Int값을 반환하도록 되어있더라도 마찬가지 입니다. 두 개의 옵셔널 체이닝이 차례로 있지만 결과는 하나의 옵셔널로 래핑 됩니다. 여러 개의 옵셔널 체이닝에 관한 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/52&quot;&gt;Linking Multiple Levels of Chaining&lt;/a&gt;을 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;increment(forCount:)&lt;/code&gt;을 호출한 뒤 반환하는 옵셔널 Int는 옵셔널 바인딩을 사용하여 &lt;code&gt;amount&lt;/code&gt;라는 상수로 래핑 해제됩니다. 옵셔널 Int에 값이 있으면 래핑 되지 않은 금액이 &lt;code&gt;count&lt;/code&gt; 프로퍼티에 추가되고 증가가 완료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dataSource&lt;/code&gt;가 nil이거나 &lt;code&gt;increment(forCount:)&lt;/code&gt;를 구현하지 않아서 &lt;code&gt;increment(forCount:)&lt;/code&gt;의 값을 얻을 수 없는 경우 &lt;code&gt;increment()&lt;/code&gt; 메서드는 &lt;code&gt;dataSource&lt;/code&gt;의 &lt;code&gt;fixedIncrement&lt;/code&gt; 프로퍼티에서 값을 가져오려고 합니다. &lt;code&gt;fixedIncrement&lt;/code&gt; 프로퍼티도 옵셔널 요구사항이므로 해당 값이 &lt;code&gt;CounterDataSource&lt;/code&gt; protocol에서 Int 타입으로 정의되어 있더라도 해당 값은 옵셔널 Int 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 dataSource가 쿼리 될 때마다 상수 값 3을 반환하는 간단한 &lt;code&gt;CounterDataSource&lt;/code&gt; 구현입니다. 이는 옵셔널 요구사항인 &lt;code&gt;fixedIncrement&lt;/code&gt; 프로퍼티를 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ThreeSource&lt;/code&gt;의 인스턴스를 새로운 &lt;code&gt;Counter&lt;/code&gt; 인스턴스의 &lt;code&gt;dataSource&lt;/code&gt;로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 새로운 &lt;code&gt;Counter&lt;/code&gt; 인스턴스를 생성하고 &lt;code&gt;dataSource&lt;/code&gt;를 새로운 &lt;code&gt;ThreeSource&lt;/code&gt; 인스턴스로 설정합니다. &lt;code&gt;counter&lt;/code&gt;의 &lt;code&gt;increment()&lt;/code&gt; 메서드를 네 번 호출하면 예상대로 &lt;code&gt;counter&lt;/code&gt;의 &lt;code&gt;count&lt;/code&gt; 프로퍼티는 &lt;code&gt;increment()&lt;/code&gt;가 호출될 때마다 3씩 증가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;Counter&lt;/code&gt; 인스턴스가 현재 &lt;code&gt;count&lt;/code&gt; 값에서 0을 향해 증가 혹은 감소하도록 하는 &lt;code&gt;TowardsZeroSource&lt;/code&gt;라는 좀 더 복잡한 data source입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -&amp;gt; Int {
        if count == 0 {
            return 0
        } else if count &amp;lt; 0 {
            return 1
        } else {
            return -1
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;TowardsZeroSource&lt;/code&gt; class는 &lt;code&gt;CounterDataSource&lt;/code&gt; protocol의 옵셔널 &lt;code&gt;increment(forCount:)&lt;/code&gt; 메서드를 구현하고 &lt;code&gt;count&lt;/code&gt; 매개변수를 사용하여 계산할 방향을 계산합니다. &lt;code&gt;count&lt;/code&gt;가 이미 0 이면 메서드는 0을 반환하고 더 이상 계산이 발생하지 않아야 한다고 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 &lt;code&gt;Counter&lt;/code&gt; 인스턴스와 함께 &lt;code&gt;TowardsZeroSource&lt;/code&gt;의 인스턴스를 사용해서 -4에서 0까지 계산할 수 있습니다. &lt;code&gt;count&lt;/code&gt;가 0이 되면 더 이상 계산을 하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    print(counter.count)
}
// -3
// -2
// -1
// 0
// 0&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol Extensions&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol은 메서드, 생성자, 서브 스크립트, 계산 프로퍼티 구현을 자신을 채택한 타입에게 제공하려면 extension을 사용하면 됩니다. 이를 통해 각 타입의 적합성, 전역 함수가 아닌 protocol 자체에 대한 동작을 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &lt;code&gt;RandomNumberGenerator&lt;/code&gt; protocol에 extension을 사용하여 &lt;code&gt;randomBool()&lt;/code&gt;메서드를 제공하도록 할 수 있습니다. 이 메서드는 필수적인 &lt;code&gt;random()&lt;/code&gt;메서드의 결과를 사용하여 임의의 Bool 값을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension RandomNumberGenerator {
    func randomBool() -&amp;gt; Bool {
        return random() &amp;gt; 0.5
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol에 extension을 사용함으로써 protocol을 채택한 모든 타입은 별다른 수정 없이 &lt;code&gt;randomBool()&lt;/code&gt;메서드를 사용할 수 있게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;let generator = LinearCongruentialGenerator()
print(&quot;Here's a random number: \(generator.random())&quot;)
// Prints &quot;Here's a random number: 0.3746499199817101&quot;
print(&quot;And here's a random Boolean: \(generator.randomBool())&quot;)
// Prints &quot;And here's a random Boolean: true&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol의 extension은 자신을 채택한 타입에 구현을 추가할 수 있지만 다른 protocol를 상속할 수는 없습니다. Protocol의 상속은 항상 protocol을 선언할 때만 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Providing Default Implementations&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol extension을 사용해서 해당 protocol가 요구하는 모든 메서드 또는 계산 프로퍼티에 대한 기본 구현을 제공할 수 있습니다. 자신을 채택한 타입이 요구사항을 이미 구현한 경우에는 protocol extension에서 구현한 것 말고 해당 타입 내에서 구현한 것이 사용됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: extension에서 protocol의 요구사항을 구현하는 것은 옵셔널 protocol 요구사항과는 다릅니다. 채택한 타입이 자체적으로 요구사항을 구현할 필요는 없지만 이미 protocol extension에 구현이 되어있으므로 옵셔널 체이닝 없이 바로 사용할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;code&gt;TextRepresentable&lt;/code&gt; protocol을 상속하는 &lt;code&gt;PrettyTextRepresentable&lt;/code&gt; protocol은 &lt;code&gt;prettyTextualDescription&lt;/code&gt; 프로퍼티의 default 구현을 제공하여 단순하게 &lt;code&gt;textualDescription&lt;/code&gt; 프로퍼티에 접근한 결과를 반환할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Adding Constraints to Protocol Extensions&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol extension을 정의할 때 해당 타입이 extension의 메서드와 프로퍼티를 사용할 수 있기 전에 충족해야 하는 제약조건을 지정할 수 있습니다. 제네릭 where 절을 사용하여 extension을 사용할 protocol 이름 뒤에 제약조건을 작성할 수 있는데요, generic where절에 대한 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/69&quot;&gt;Generic Where Clauses&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Element가 &lt;code&gt;Equatable&lt;/code&gt; protocol을 준수하는 모든 컬렉션에만 적용되는 protocol extension을 정의할 수 있습니다. 컬렉션의 element를 표준 라이브러리의 일부인 &lt;code&gt;Equatable&lt;/code&gt; protocol로 제한하면 &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt; 연산자를 사용하여 두 Element 간 비교를 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension Collection where Element: Equatable {
    func allEqual() -&amp;gt; Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;allEqual()&lt;/code&gt; 메서드는 컬렉션의 모든 Element가 동일한 경우에만 true를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 Int Array에 직접 사용해봅시다. 하나는 다른 모든 Element가 동일하지만 다른 하나는 그렇지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Int 타입은 &lt;code&gt;Equatable&lt;/code&gt;을 준수하기 때문에 &lt;code&gt;equalNumbers&lt;/code&gt;, &lt;code&gt;differentNumbers&lt;/code&gt;는 &lt;code&gt;allEqual()&lt;/code&gt; 메서드를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;print(equalNumbers.allEqual())
// Prints &quot;true&quot;
print(differentNumbers.allEqual())
// Prints &quot;false&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: 어떤 타입이 동일한 메서드, 프로퍼티에 대한 구현을 제공하는 여러 개의 제약된 extension의 조건을 만족하는 경우 Swift는 가장 specialezed 한 제약조건에 해당하는 구현을 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Swift/Swift_Documents</category>
      <category>IOS</category>
      <category>optional</category>
      <category>Protocol</category>
      <category>required</category>
      <category>Swift</category>
      <category>기초</category>
      <category>문법</category>
      <category>앱개발</category>
      <category>프로그래밍</category>
      <category>프로토콜</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/62</guid>
      <comments>https://icksw.tistory.com/62#entry62comment</comments>
      <pubDate>Sat, 19 Feb 2022 23:30:35 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Subscription - Combine 공부 6</title>
      <link>https://icksw.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/275&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Apple에서 Subscriber 사용을 위해 미리 정의해둔 것들에 대해 알아봤었는데요, 이번 글에서는 이전에 배운 Publisher, Subscriber를 연결하는 역할을 하는 Subscription에 대해 알아보려고 합니다.&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;Subscription이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscription은 정의부터 어떤 녀석인지 느낌이 옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLXOTI/btrrd6zD1uJ/tu4MckcJpOyHL3NhskY8K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLXOTI/btrrd6zD1uJ/tu4MckcJpOyHL3NhskY8K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLXOTI/btrrd6zD1uJ/tu4MckcJpOyHL3NhskY8K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLXOTI%2Fbtrrd6zD1uJ%2Ftu4MckcJpOyHL3NhskY8K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;476&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscription도 프로토콜이며 Cancellable이라는 걸 채택했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Subscriber와 Publisher의 연결을 나타내는 프로토콜이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 좀 더 보면 Subscription에는 특정 Subscriber가 Publisher를 subscribe 할 때 정의되는 ID가 있어서 Class로만 정의해야 한다고 합니다. 또한 Subscription을 cancel 하는 작업은 스레드로부터 안전해야 한다고 하며 cancel은 한 번만 할 수 있다고 해요. Subscription을 cancel 하면 Subscriber를 연결해서 할당된 모든 리소스도 해제된다고 합니다.&lt;/p&gt;
&lt;p 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_1642608316480&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Subscription : Cancellable, CustomCombineIdentifierConvertible {
    func request(_ demand: Subscribers.Demand)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;request(_ demand: Subscribers.Demand)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher에게 Subscriber가 값을 요구하는 횟수를 알려줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의는 되게 간단합니다. Publisher와 Subscriber를 연결하는 역할을 하다보니 그 둘 사이의 소통을 담당하는 request 메서드만 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscription 프로토콜이 채택하고 있던 다른 프로토콜들인 Cancellable, CustomCombineIdentifierConvertible도 간단히 살펴보겠습니다.&lt;/p&gt;
&lt;h1 data-v-643b4402=&quot;&quot;&gt;Cancellable&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cancellable의 정의를 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9UMNw/btrq8sYtZJz/YNYc6KZAZvFhzkJflaP7AK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9UMNw/btrq8sYtZJz/YNYc6KZAZvFhzkJflaP7AK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9UMNw/btrq8sYtZJz/YNYc6KZAZvFhzkJflaP7AK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9UMNw%2Fbtrq8sYtZJz%2FYNYc6KZAZvFhzkJflaP7AK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;318&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 간단하게 cancel을 할 수 있는 녀석을 나타내는 프로토콜이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현을 살펴봐도 간단합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1642608707066&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Cancellable {
    func cancel()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 간단하게 cancel() 메서드만 있네요.&lt;/p&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 Publisher를 구현하기 위해 Cancellable을 만들 때 cancel() 메서드는 Publisher가 downstream의 Subscriber request를 중지하는 작업을 수행하면 된다고 합니다. Combine은 Publisher가 즉시 멈추는 것을 요구하지는 않지만 cancel() 메서드의 호출은 빠르게 적용된다고 하네요. 그리고 아까 언급한 대로 cancel()은 첫 호출 때만 작동하고 그 이후에는 작동되면 안 된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cancellable의 extension 구현에 자주 보던게 있어서 그거도 가지고 와 봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1642690763288&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Cancellable {
    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    public func store&amp;lt;C&amp;gt;(in collection: inout C) where C : RangeReplaceableCollection, C.Element == AnyCancellable

    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    public func store(in set: inout Set&amp;lt;AnyCancellable&amp;gt;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘는 Subscription을 저장할 때 사용하는 메서드입니다. Subscription을 만들고 어딘가에 저장해놓지 않으면 메모리에서 할당 해제되어 동작되지 않는데, store() 메서드는 이를 방지하기 위해 Subscription 저장을 할 때 자주 사용했었어요.&lt;/p&gt;
&lt;p 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_1642938906693&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()
let stringArray: [String] = [&quot;Pingu&quot;, &quot;Pinga&quot;]
stringArray.publisher
    .sink(receiveValue: { print($0) })
    .store(in: &amp;amp;subscriptions)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 사용하면 subscriptions에 subscription이 저장되게 됩니다. 근데 왜 타입이 AnyCancellable이냐?면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds7Fl3/btrrwe5srVc/PKDvgkv9klZvaZ42QFGwq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds7Fl3/btrrwe5srVc/PKDvgkv9klZvaZ42QFGwq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds7Fl3/btrrwe5srVc/PKDvgkv9klZvaZ42QFGwq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds7Fl3%2Fbtrrwe5srVc%2FPKDvgkv9klZvaZ42QFGwq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;70&quot; data-origin-width=&quot;363&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;이렇게 예전에 알아본 sink가 반환하는 타입이 AnyCancellable이기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AnyCancellable&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 AnyCancellable이 뭔지 정의를 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/comLx5/btrrunImwv2/B3tWqNgno5EwrsWGjRCvjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/comLx5/btrrunImwv2/B3tWqNgno5EwrsWGjRCvjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/comLx5/btrrunImwv2/B3tWqNgno5EwrsWGjRCvjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcomLx5%2FbtrrunImwv2%2FB3tWqNgno5EwrsWGjRCvjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;196&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cancellable 객체의 타입의 타입을 지운 것이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 알아본 AnySubscriber, AnyPublisher와 비슷하게 모든 Cancellable도 AnyCancellable로 쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;h1 data-v-643b4402=&quot;&quot;&gt;Custom Combine Identifier Convertible&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘도 Subscription이 채택한 프로토콜이었는데, 정의를 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nlc2g/btrricmVN4A/w66B2pezLNMK4LMkrzhwV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nlc2g/btrricmVN4A/w66B2pezLNMK4LMkrzhwV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nlc2g/btrricmVN4A/w66B2pezLNMK4LMkrzhwV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNlc2g%2FbtrricmVN4A%2Fw66B2pezLNMK4LMkrzhwV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;225&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름에서 알 수 있듯이 Publisher Stream을 식별하기 위한 프로토콜이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 Subscription들이 연결된 Publisher 체인에서 이 프로토콜을 사용해서 Subscription을 식별할 수 있다고 하네요.&lt;/p&gt;
&lt;p 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_1642692016152&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol CustomCombineIdentifierConvertible {
    var combineIdentifier: CombineIdentifier { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 뭔가 Hashable을 채택했을 것만 같은 프로퍼티 하나가 있네요. 저거로 구분하나 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Subscription을 한 번 직접 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Publisher의 Input, Subscriber의 Output으로 사용할 객체를 하나 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1642950810342&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct YoutubeSubscriber {
    let name: String
    let age: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 객체를 사용할 Subscription과 Publisher를 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1642951344624&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Subscription
final class PinguSubscription&amp;lt;S: Subscriber&amp;gt;: Subscription where S.Input == YoutubeSubscriber {
    var requested: Subscribers.Demand = .none
    var youtubeSubscribers: [YoutubeSubscriber]
    var subscriber: S?
    
    init(subscriber: S,
         youtubeSubscribers: [YoutubeSubscriber]) {
        print(&quot;PinguSubscription이 생성됨!&quot;)
        self.subscriber = subscriber
        self.youtubeSubscribers = youtubeSubscribers
    }
    
    func request(_ demand: Subscribers.Demand) {
        print(&quot;요청받은 demand : \(demand)&quot;)
        for youtubeSubscriber in youtubeSubscribers {
            subscriber?.receive(youtubeSubscriber)
        }
        
//        subscriber?.receive(completion: .finished)
    }
    
    func cancel() {
        print(&quot;PinguSubscription이 cancel됨!&quot;)
        youtubeSubscribers.removeAll()
        subscriber = nil
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1642951361652&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Publisher
extension Publishers {
    struct PinguPublisher: Publisher {
        var youtubeSubscribers: [YoutubeSubscriber]
        
        func receive&amp;lt;S&amp;gt;(subscriber: S)
        where S : Subscriber, Never == S.Failure, YoutubeSubscriber == S.Input {
            let subscription = PinguSubscription(subscriber: subscriber, youtubeSubscribers: youtubeSubscribers)
            subscriber.receive(subscription: subscription)
        }
        
        typealias Output = YoutubeSubscriber
        typealias Failure = Never
        
        mutating func append(subscriber: YoutubeSubscriber) {
            youtubeSubscribers.append(subscriber)
        }
    }
    
    static func pingu(youtubeSubscribers: [YoutubeSubscriber]) -&amp;gt; Publishers.PinguPublisher {
        return Publishers.PinguPublisher(youtubeSubscribers: youtubeSubscribers)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만들어주면 준비는 끝!입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PinguSubscription 코드를 조금 살펴보면, request(), cancel()을 직접 구현해놓은 것을 볼 수 있습니다. 그리고 cancel()이 호출되는 것을 볼 수 있도록 finished 전달 코드는 주석 처리해뒀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그렇게 만든 PinguSubscription을 PinguPublisher의 receive(subsriber:)에서 subscriber에게 전달해줍니다.&lt;/p&gt;
&lt;p 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_1642951456706&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()
let youtubeSubscriber1 = YoutubeSubscriber(name: &quot;Pingu&quot;, age: 7)
let youtubeSubscriber2 = YoutubeSubscriber(name: &quot;Pinga&quot;, age: 5)

var youtubeSubscribers = [youtubeSubscriber1, youtubeSubscriber2]

let pinguPublisher = Publishers.pingu(youtubeSubscribers: youtubeSubscribers)
pinguPublisher
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print(&quot;name: \($0.name), age: \($0.age)&quot;) }
    )
    .store(in: &amp;amp;subscriptions)

for subscription in subscriptions {
    subscription.cancel()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 사용할 수 있고 실행해보면 결과는 다음과 같이 나옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1642951489339&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PinguSubscription이 생성됨!
요청받은 demand : unlimited
name: Pingu, age: 7
name: Pinga, age: 5
PinguSubscription이 cancel됨!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Subscription의 메서드들을 직접 구현하고 호출되는 것을 확인할 수 있습니다. 어떻게 응용하느냐에 따라 정말 다양하게 사용할 수 있을 거 같아요.&lt;/p&gt;
&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://icksw.tistory.com/277&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt; 부터는 여러가지 Operator에 대해 하나씩 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Subscription.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>IOS</category>
      <category>Publisher</category>
      <category>subscriber</category>
      <category>Subscription</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/276</guid>
      <comments>https://icksw.tistory.com/276#entry276comment</comments>
      <pubDate>Mon, 24 Jan 2022 01:01:17 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Subscriber 사용을 위해 미리 정의된 것들 - Combine 공부 5</title>
      <link>https://icksw.tistory.com/275</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/274&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Combine을 사용하지 않던 코드에서 간단하게 Combine을 적용하고 싶을 때 사용하면 좋은 Subject를 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;a href=&quot;https://icksw.tistory.com/273&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;서 알아본 미리 정의된 Publisher와 같이 미리 정의된 Subscriber에는 뭐가 있는지 살펴보려고 합니다.&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;Subscriber란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 Subscriber가 뭔지 다시 알아볼게요.&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;970&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csBSiN/btrp6wgSybH/EyzQwKBDJTWPKKfF1iNoUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csBSiN/btrp6wgSybH/EyzQwKBDJTWPKKfF1iNoUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csBSiN/btrp6wgSybH/EyzQwKBDJTWPKKfF1iNoUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsBSiN%2Fbtrp6wgSybH%2FEyzQwKBDJTWPKKfF1iNoUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;210&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 간단하게 말해서 Publisher에게 값을 받기 위해 선언해둔 프로토콜이라고 할 수 있습니다.&lt;/p&gt;
&lt;p 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_1641718884218&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Subscriber : CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure: Error
    
    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -&amp;gt; Subscribers.Demand
    func receive(completion: Subscribers.Completion&amp;lt;Self.Failure&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Input
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher에게 받는 값의 타입입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Failure
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher에게 받는 Error 타입입니다.&lt;/li&gt;
&lt;li&gt;만약 Error를 수신하지 않고 싶다면 Never 타입으로 설정해주면 됩니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(subscription:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 만들어서 주는 subscription을 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(input:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 주는 값을 받습니다.&lt;/li&gt;
&lt;li&gt;Demand를 반환하는데 이는 값을 더 원하는지에 대한 여부입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(completion:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 주는 completion event를 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 값을 받으려는 Publisher의 &amp;lt;Output, Failure&amp;gt;의 타입이 Subscriber의 &amp;lt;Input, Failure&amp;gt; 타입과 일치해야 한다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 여기 보면 receive(input:) 메서드의 반환 타입이 Subscribers.Demand였고, 여기 나오는 Subscribers에 있는 애들이 바로 이번 글에서 알아볼 미리 정의된 Subscriber들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 종류는 다음과 같아요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Demand&lt;/li&gt;
&lt;li&gt;Completion&lt;/li&gt;
&lt;li&gt;Sink&lt;/li&gt;
&lt;li&gt;Assign&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sink (class)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글의 예제에서도 자주 보였던 Sink부터 알아보도록 하겠습니다.&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;962&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boxVUD/btrp8rTfQFv/UoqhQhkNfcqF54u9Cif39k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boxVUD/btrp8rTfQFv/UoqhQhkNfcqF54u9Cif39k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boxVUD/btrp8rTfQFv/UoqhQhkNfcqF54u9Cif39k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboxVUD%2Fbtrp8rTfQFv%2FUoqhQhkNfcqF54u9Cif39k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;212&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Sink는 횟수의 제한 없이 Subscription을 통해 값을 요청하는 간단한 Subscriber라고 합니다.&lt;/p&gt;
&lt;p 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_1641719695025&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final public class Sink&amp;lt;Input, Failure&amp;gt; : Subscriber, Cancellable, CustomStringConvertible,
                                            CustomReflectable, CustomPlaygroundDisplayConvertible
where Failure : Error {
    final public var receiveValue: (Input) -&amp;gt; Void { get }
    final public var receiveCompletion: (Subscribers.Completion&amp;lt;Failure&amp;gt;) -&amp;gt; Void { get }
    
    final public var description: String { get }
    final public var customMirror: Mirror { get }
    final public var playgroundDescription: Any { get }
    
    public init(receiveCompletion: @escaping ((Subscribers.Completion&amp;lt;Failure&amp;gt;) -&amp;gt; Void), 
                receiveValue: @escaping ((Input) -&amp;gt; Void))
    
    final public func cancel()
    
    // Subscriber 프로토콜의 필수요소들
    final public func receive(subscription: Subscription)
    final public func receive(_ value: Input) -&amp;gt; Subscribers.Demand
    final public func receive(completion: Subscribers.Completion&amp;lt;Failure&amp;gt;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;receiveValue
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 받았을 때 실행될 클로저입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receiveCompletion
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Completion을 받았을 때 실행될 클로저입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;customMirror
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 타입의 인스턴스에 대한 하위 구조 및 표시되는 스타일을 나타내는 Mirror를 커스텀할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cancel
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;말 그대로 subscription을 취소합니다.&lt;/li&gt;
&lt;li&gt;Cancellable 프로토콜을 채택하기 때문에 구현되어있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 실제로 주로 사용하는 것은 receiveValue, receiveCompletion 정도입니다.&lt;/p&gt;
&lt;p 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_1641720940602&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intArrayPublisher = [1,2,3,4,5].publisher
        
let sink = Subscribers.Sink&amp;lt;Int, Never&amp;gt;(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
                                        receiveValue: { print(&quot;value: \($0)&quot;)})

intArrayPublisher.subscribe(sink)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 보통은 위와 같이 사용하기보다는 아래와 같이 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641721093969&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let intArrayPublisher = [1,2,3,4,5].publisher

intArrayPublisher
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;)})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 코드는 sink를 직접 만들고 이를 Publisher의 subscribe로 전달했다면, 두 번째 코드는 Publisher의 extension에 구현된 sink Operator를 사용한 예입니다.&lt;/p&gt;
&lt;p 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_1641721329317&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;value: 1
value: 2
value: 3
value: 4
value: 5
completion: finished&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;두 번째 예에서 사용한 sink Operator를 사용하면 첫 번째 예에서 만든 sink를 알아서 만들고 바로 subscribe 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 만들 필요가 없어서 사용이 편리하죠!&amp;nbsp;그리고 값도 알아서 바로 request 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;description, playgroundDescription, customMirror는 어떻게 출력되는지 궁금해서 한 번 출력해봤는데, 결과는 다음과 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1641721601879&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;description: Sink
playgroundDescription: Sink
customMirror: Mirror for Sink&amp;lt;Int, Never&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Sink의 cancel()을 사용해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641722544170&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let subject = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let firstSink = Subscribers.Sink&amp;lt;Int, Never&amp;gt;(receiveCompletion: { print(&quot;first sink completion: \($0)&quot;) },
                                             receiveValue: { print(&quot;first sink value: \($0)&quot;)})
let secondSink = Subscribers.Sink&amp;lt;Int, Never&amp;gt;(receiveCompletion: { print(&quot;second sink completion: \($0)&quot;) },
                                              receiveValue: { print(&quot;second sink value: \($0)&quot;)})

subject.subscribe(firstSink)
subject.subscribe(secondSink)
subject.send(1)

// 첫 번째 sink 취소
firstSink.cancel()

subject.send(2)
subject.send(completion: .finished)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 2는 firstSink에 전달되지 않을 것 같은데, 실제로도 그런지 확인해보면..&lt;/p&gt;
&lt;pre id=&quot;code_1641722580486&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;first sink value: 1
second sink value: 1
second sink value: 2
second sink completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값은 물론 completion도 전달되지 않은 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Assign (class)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Assign을 알아볼게요.&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;964&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyh2z2/btrqaJZX7lO/0Qu46MhCVsw4jOiwKc6kZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyh2z2/btrqaJZX7lO/0Qu46MhCVsw4jOiwKc6kZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyh2z2/btrqaJZX7lO/0Qu46MhCVsw4jOiwKc6kZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcyh2z2%2FbtrqaJZX7lO%2F0Qu46MhCVsw4jOiwKc6kZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;228&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Assign은 key path로 표시된 프로퍼티에 수신된 값을 할당하는 간단한 Subscriber라고 하네요.&lt;/p&gt;
&lt;p 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_1641722888264&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final public class Assign&amp;lt;Root, Input&amp;gt; : Subscriber, Cancellable, CustomStringConvertible, 
CustomReflectable, CustomPlaygroundDisplayConvertible {
    public typealias Failure = Never
    final public var object: Root? { get }
    final public let keyPath: ReferenceWritableKeyPath&amp;lt;Root, Input&amp;gt;
    
    public init(object: Root, keyPath: ReferenceWritableKeyPath&amp;lt;Root, Input&amp;gt;)

    final public func receive(subscription: Subscription)
    final public func receive(_ value: Input) -&amp;gt; Subscribers.Demand
    final public func receive(completion: Subscribers.Completion&amp;lt;Never&amp;gt;)
    
    final public func cancel()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈에 띄는 것은 두 개네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;object
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로퍼티를 포함하는 객체라고 합니다. Subscriber는 새로운 값을 받을 때마다 여기에 할당한다고 하네요.&lt;/li&gt;
&lt;li&gt;Subscriber는 upstream publisher가 Subscriber의 receive(completion:)을 호출할 때까지 object에 대한 강한 참조를 유지하고, 호출된 이후에야 nil로 설정된다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;keyPath
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;할당할 프로퍼티를 나타내는 key-path입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 말해서 어떤 값을 받아서 어떤 곳에 저장하는 Subscirber입니다.&lt;/p&gt;
&lt;p 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_1641723832216&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SampleObject {
    var intValue: Int {
        didSet {
            print(&quot;intValue Changed: \(intValue)&quot;)
        }
    }
    
    init(intValue: Int) {
        self.intValue = intValue
    }
    
    deinit {
        print(&quot;sample object deinit&quot;)
    }
}

let myObject = SampleObject(intValue: 5)

let assign = Subscribers.Assign&amp;lt;SampleObject, Int&amp;gt;(object: myObject, keyPath: \.intValue)

let intArrayPublisher = [6,7,8,9].publisher
intArrayPublisher.subscribe(assign)
print(myObject.intValue)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 SampleObject라는 간단한 클래스를 하나 만들고 그 안에 intValue라는 프로퍼티를 만들어줍니다. 그리고 해당 프로퍼티에 프로퍼티 옵저버를 사용해서 값이 변할 때마다 현재 값이 print 되도록 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤에 assign을 만들고 [6, 7, 8, 9] 배열의 값을 내보내는 publisher를 만든 후에 assign으로 subscribe 하면 다음과 같은 결과를 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641723966163&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;intValue Changed: 6
intValue Changed: 7
intValue Changed: 8
intValue Changed: 9
9
sample object deinit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher가 내보내는 값을 계속해서 할당하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이를 쉽게 사용하기 위해 Publisher에는 assign이라는 Operator가 구현되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 사용해서 동일한 동작을 하는 코드를 만들어 보면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641725411579&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SampleObject {
    var intValue: Int {
        didSet {
            print(&quot;intValue Changed: \(intValue)&quot;)
        }
    }
    
    init(intValue: Int) {
        self.intValue = intValue
    }
    
    deinit {
        print(&quot;sample object deinit&quot;)
    }
}

let myObject = SampleObject(intValue: 5)

let intArrayPublisher = [6,7,8,9].publisher

intArrayPublisher
    .assign(to: \.intValue, on: myObject)
    
print(myObject.intValue)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 아까 object를 강한 참조로 가지고 있다고 했잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 강한 참조 주기가 발생하는 경우가 발생하기도 하는데요, 이건 개인적으로 좀 더 본 뒤에 다음 글에서 알아보도록 할게요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Demand (struct)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 것은 Demand입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dS6iWG/btrp96uAD0m/Ul4eW5quKaCZBywhtUo5Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dS6iWG/btrp96uAD0m/Ul4eW5quKaCZBywhtUo5Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dS6iWG/btrp96uAD0m/Ul4eW5quKaCZBywhtUo5Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdS6iWG%2Fbtrp96uAD0m%2FUl4eW5quKaCZBywhtUo5Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;228&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Subscriber가 subscription을 통해 Publisher에게 요청한 item의 수입니다.&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_1641725950211&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@frozen public struct Demand : Equatable, Comparable, Hashable, Codable, 
CustomStringConvertible {
    public static let unlimited: Subscribers.Demand
    public static let none: Subscribers.Demand
    @inlinable public static func max(_ value: Int) -&amp;gt; Subscribers.Demand
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber는 receive(input:)의 반환 값으로 Demand를 반환한다고 했는데요, 반환된 Demand 값에 따라 값을 더 요청할지 그만 할지를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 구현부에 정의된 것을 살펴보면..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;unlimited
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계속해서 값을 받겠다는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;none&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;max(0)과 같은 의미입니다. (요청을 추가하지 않는다는 뜻)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;max(_ value: Int)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매개변수로 받은 값만큼 추가로 요청합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber가 값을 요청할 횟수를 subscription의 request(_:)를 처음 호출할 때 정할 수도 있지만 receive(input:)에서 추가할 수도 있습니다. 그리고 횟수를 감소시키는 것은 불가능합니다. 예를 들어 max(-3)을 반환하면 fatalError가 발생합니다. 그리고 Demand는 누적되는 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Demand는 Subscriber 프로토콜을 채택한 애는 아니고 그냥 Subscriber를 사용할 때 필요한 애라고 할 수 있는데요, 그래서 사용법도 앞서 본 sink, assign과 다릅니다. Subscriber를 구현해서 receive(input:)이나 receive(subscription:)에서 사용해줘야 해요.&lt;/p&gt;
&lt;pre id=&quot;code_1641727287261&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DemandTestSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    
    func receive(subscription: Subscription) {
        print(&quot;subscribe 시작!&quot;)
        // 여기서 Demand를 설정해줄 수도 있어요!
        // 현재 요청횟수는 1
        subscription.request(.max(1))
    }
    
    func receive(_ input: Int) -&amp;gt; Subscribers.Demand {
        print(&quot;receive input: \(input)&quot;)
        
        // input 값이 2일때만 요청횟수를 1 추가합니다.
        if input == 2 {
            return .max(1)
        } else {
            return .none
        }
    }
    
    func receive(completion: Subscribers.Completion&amp;lt;Never&amp;gt;) {
        print(&quot;receive completion: \(completion)&quot;)
    }
}

let publisher = [2, 3, 4, 5].publisher
publisher
    .print()
    .subscribe(DemandTestSubscriber())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구현한 DemandTestSubscriber의 경우 처음 subscribe 할 때 요청 횟수를 1로 설정하고 이후에는 수신한 값이 2일 때만 요청횟수를 1 증가시키게 됩니다.&amp;nbsp;publisher에서 사용한 print() Operator는 해당 subscription에서 발생하는 동작을 보여주는 Operator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행하면 subscriber는 다음과 같이 2개의 값만 받을 수 있게 되죠.&lt;/p&gt;
&lt;pre id=&quot;code_1641727377111&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;receive subscription: ([2, 3, 4, 5])
subscribe 시작!
request max: (1)
receive value: (2)
receive input: 2
request max: (1) (synchronous)
receive value: (3)
receive input: 3&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;receive(subscription:)에서 처음 subscription을 받을 때 Demand를 unlimited로 해두고 receive(input:)에서는 항상 .none을 반환하면 어떻게 될까요?&lt;/p&gt;
&lt;pre id=&quot;code_1641727561789&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DemandTestSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    
    func receive(subscription: Subscription) {
        print(&quot;subscribe 시작!&quot;)
        // 여기서 Demand를 설정해줄 수도 있어요!
        subscription.request(.unlimited)
    }
    
    func receive(_ input: Int) -&amp;gt; Subscribers.Demand {
        return .none
    }
    
    func receive(completion: Subscribers.Completion&amp;lt;Never&amp;gt;) {
        print(&quot;receive completion: \(completion)&quot;)
    }
}

let publisher = [2, 3, 4, 5].publisher
publisher
    .print()
    .subscribe(DemandTestSubscriber())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641727574056&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;receive subscription: ([2, 3, 4, 5])
subscribe 시작!
request unlimited
receive value: (2)
receive value: (3)
receive value: (4)
receive value: (5)
receive finished
receive completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 계속해서 값을 요청하겠다고 설정했으니 이후에 항상 Demand로 none을 반환해도 모든 값을 수신할 수 있는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Demand는 누적되는 값이다, 음수를 넣어서 감소시킬 수는 없다! 정도만 알면 사용할 때 큰 문제는 없겠어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Completion (enum)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 것은 Completion입니다. 얘도 Demand와 동일하게 Subscriber는 아니고 Subscriber를 사용할 때 필요한 녀석인데요, 아까 DemandTestSubscriber를 직접 구현할 때 receive(completion:)에서 썼었습니다 ㅎㅎ&lt;/p&gt;
&lt;p 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;978&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQprnb/btrp3K7tdtm/MZ9n6NMRph5HIQoVkNKAW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQprnb/btrp3K7tdtm/MZ9n6NMRph5HIQoVkNKAW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQprnb/btrp3K7tdtm/MZ9n6NMRph5HIQoVkNKAW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQprnb%2Fbtrp3K7tdtm%2FMZ9n6NMRph5HIQoVkNKAW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;242&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보니 정상적인 완료 혹은 에러로 인해 Publisher가 값을 더 이상 생성하지 않는다는 신호! 라네요.&lt;/p&gt;
&lt;p 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_1641728909804&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@frozen public enum Completion&amp;lt;Failure&amp;gt; where Failure : Error {
    case finished
    case failure(Failure)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하네요. finished는 정상적인 완료일 때, failure가 실패일 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 모두 Failure타입을 Never로 사용해서 에러가 없었는데, 에러가 있는 Subscriber를 한 번 만들어볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1641729353348&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// custom Error를 만듭니다.
enum PinguError: Error {
    case pinguIsBaboo
}

class PinguSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = PinguError
    
    func receive(subscription: Subscription) {
        subscription.request(.unlimited)
    }
    
    func receive(_ input: Int) -&amp;gt; Subscribers.Demand {
        print(&quot;receive input: \(input)&quot;)
        return .none
    }
    
    func receive(completion: Subscribers.Completion&amp;lt;PinguError&amp;gt;) {
        // .pinguIsBaboo 수신시 실행
        if completion == .failure(.pinguIsBaboo) {
            print(&quot;Pingu는 바보입니다.&quot;)
        } else {
            print(&quot;finished!&quot;)
        }
    }
}

let subject = PassthroughSubject&amp;lt;Int, PinguError&amp;gt;()
let subscriber = PinguSubscriber()

subject.subscribe(subscriber)

subject.send(100)
subject.send(completion: .failure(.pinguIsBaboo))
subject.send(200)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Error를 하나 구현하고 Subscriber의 Failure 타입을 해당 에러로 설정하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 해당 Subscriber가 특정 에러를 받을 때 원하는 작업을 실행하도록 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행해보면 아래와 같은 결과를 볼 수 있을 거예요.&lt;/p&gt;
&lt;pre id=&quot;code_1641729570200&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;receive input: 100
Pingu는 바보입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber가 failure completion을 받았기 때문에 값이나 completion을 받지 않으므로 마지막에 전달한 200은 전달되지 않는 것을 볼 수 있어요.&lt;/p&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을 사용하면서 에러 처리를 잘할 수 있을 거 같네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AnySubscriber&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 AnySubscriber를 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher를 공부할 때도 AnyPublisher가 있었는데요, 얘도 비슷합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HQdDm/btrqih2XtOg/NXmYIKL9TCwClnJQGj8b9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HQdDm/btrqih2XtOg/NXmYIKL9TCwClnJQGj8b9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HQdDm/btrqih2XtOg/NXmYIKL9TCwClnJQGj8b9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHQdDm%2Fbtrqih2XtOg%2FNXmYIKL9TCwClnJQGj8b9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;206&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의는 위와 같고 말 그대로 어떤 subscriber의 타입을 간단하게 사용할 수 있도록 래핑 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용은 아까 구현한 PinguSubscriber를 재활용해서 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1641730237983&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let pinguSubscriber = PinguSubscriber()
let anySubscriber = AnySubscriber(pinguSubscriber)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 pinguSubscriber의 타입은 아래와 같은데,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tOWdd/btrqgcnjmZt/ewdL66PQtSIzQtiX6nWBhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tOWdd/btrqgcnjmZt/ewdL66PQtSIzQtiX6nWBhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tOWdd/btrqgcnjmZt/ewdL66PQtSIzQtiX6nWBhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtOWdd%2FbtrqgcnjmZt%2FewdL66PQtSIzQtiX6nWBhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;124&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;anySubscriber의 타입은 AnySubscriber로 래핑 된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3HTI/btrqbF4BosI/7N1a4oH1K7PxYH0FubI3Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3HTI/btrqbF4BosI/7N1a4oH1K7PxYH0FubI3Gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3HTI/btrqbF4BosI/7N1a4oH1K7PxYH0FubI3Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3HTI%2FbtrqbF4BosI%2F7N1a4oH1K7PxYH0FubI3Gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;152&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Apple에서 미리 만들어둔 Subscriber인 Sink, Assign과 Subscriber 사용을 위해 필요한 Demand, Completion에 대해 알아봤습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icksw.tistory.com/276&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Subscription에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/PreDefinedSubscriber.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>AnySubscriber</category>
      <category>assign</category>
      <category>Combine</category>
      <category>completion</category>
      <category>Demand</category>
      <category>Publisher</category>
      <category>sink</category>
      <category>subscriber</category>
      <category>Subscription</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/275</guid>
      <comments>https://icksw.tistory.com/275#entry275comment</comments>
      <pubDate>Sun, 9 Jan 2022 21:23:59 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Subject  - Combine 공부 4</title>
      <link>https://icksw.tistory.com/274</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/273&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Apple에서 미리 정의해둔 Publisher들을 알아봤었는데, 이번 글에서는 이어서 Publisher 프로토콜을 채택하는 또 다른 녀석들인 Subject들에 대해서 알아보려고 합니다.&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;Subject&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Subject의 정의를 볼까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DGWjE/btrpHFqy0lI/IkSaTKCThLse9cKa5MsKAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DGWjE/btrpHFqy0lI/IkSaTKCThLse9cKa5MsKAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DGWjE/btrpHFqy0lI/IkSaTKCThLse9cKa5MsKAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDGWjE%2FbtrpHFqy0lI%2FIkSaTKCThLse9cKa5MsKAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;354&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subject도 프로토콜입니다. Publisher를 채택한 프로토콜이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑에 설명에 보면 &quot;Subject는 stream에 send(_:) 메서드를 호출해서 값을 주입할 수 있는 Publisher이다.&quot;라고 적혀있네요. 그래서 기존에 Combine을 사용하지 않던 코드에 Combine 모델을 적용하고 싶을 때 사용하면 좋다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 뭔지는 알겠으니 Subject 프로토콜의 구현을 좀 더 살펴볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1641221492910&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Subject : AnyObject, Publisher {
    func send(_ value: Self.Output)
    func send(completion: Subscribers.Completion&amp;lt;Self.Failure&amp;gt;)
    func send(subscription: Subscription)
}

extension Subject Where Self.Output == Void {
    public func send()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 총 3개의 필수적인 send 메서드와 Output이 Void일 때 필요한 send() 메서드가 구현되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 필수적인 send 메서드를 보면 value, completion, subscription을 보내기 위한 것들 같네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 여기서 값들을 보내지는 곳은 Subscriber 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Subject도 프로토콜이니까 Apple이 이걸 채택한 뭔가를 만들어 놨겠죠??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 알아보면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CurrentValueSubject
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 값을 래핑하고 값이 변경할 때마다 새로운 값을 내보내는 Subject&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PassthroughSubject
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Downstream Subscriber에게 값을 전파하는 Subject&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 정의는 위와 같은데 하나씩 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CurrentValueSubject&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CurrentValueSubject 정의를 먼저 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSQpeY/btrpLtJTUxW/PdFXpKKIo7u42RK53WzVKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSQpeY/btrpLtJTUxW/PdFXpKKIo7u42RK53WzVKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSQpeY/btrpLtJTUxW/PdFXpKKIo7u42RK53WzVKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSQpeY%2FbtrpLtJTUxW%2FPdFXpKKIo7u42RK53WzVKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;446&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명에 보면 아직 자세히 알아보지 않은 PassthroughSubject와 비교하는 게 나오네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CurrentValueSubject는 PassthroughSubject와 다르게 가장 최근에 published 된 값의 버퍼를 유지한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 CurrentValueSubject의 send(_:)를 호출하면 현재 값도 업데이트돼서 값을 직접 업데이트하는 거랑 동일한 효과를 얻을 수 있다고 하네요.&lt;/p&gt;
&lt;p 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_1641222343220&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
final public class CurrentValueSubject&amp;lt;Output, Failure&amp;gt; : Subject where Failure : Error {
    final public var value: Output
    
    public init(_ value: Output)

    final public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
    
    final public func send(subscription: Subscription)
    final public func send(_ input: Output)
    final public func send(completion: Subscribers.Completion&amp;lt;Failure&amp;gt;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CurrentValueSubject의 구현을 보면 Publisher, Subject에 필요한 메서드들이 존재하고, 예상대로 최신 값을 저장하기 위한 value라는 프로퍼티가 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 생성자를 보면 최신값을 받도록 되어있는데요, 즉 CurrentValueSubject에 값이 없을 수는 없나 봅니다.&lt;/p&gt;
&lt;p 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_1641222959527&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let currentValueSubject = CurrentValueSubject&amp;lt;String, Never&amp;gt;(&quot;Pingu 첫번째 값&quot;)
currentValueSubject
    .sink(receiveCompletion: { print(&quot;1 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;1 번째 sink value: \($0)&quot;) })

currentValueSubject
    .sink(receiveCompletion: { print(&quot;2 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;2 번째 sink value: \($0)&quot;) })
    

currentValueSubject
    .sink(receiveCompletion: { print(&quot;3 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;3 번째 sink value: \($0)&quot;) })

// 현재 Subscriber들에게 모두 보냄
currentValueSubject.send(&quot;Pingu 두번째 값&quot;)
currentValueSubject.send(completion: .finished)

print(currentValueSubject.value)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서가 보장되는지 확인하려고 3개의 Subscriber를 만들었는데요, 테스트해보니 send가 subscriber에 도착하는 순서는 딱히 보장되지 않는 거 같습니다.&lt;/p&gt;
&lt;p 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_1641223029975&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1 번째 sink value: Pingu 첫번째 값
2 번째 sink value: Pingu 첫번째 값
3 번째 sink value: Pingu 첫번째 값
3 번째 sink value: Pingu 두번째 값
1 번째 sink value: Pingu 두번째 값
2 번째 sink value: Pingu 두번째 값
3 번째 sink completion: finished
1 번째 sink completion: finished
2 번째 sink completion: finished
Pingu 두번째 값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 사용해보면 알 수 있는 것은 자신은 subsribe 하는 모든 subscriber에게 send를 통해서 값을 보낸다는 점과 send메서드를 호출하면 CurrentValueSubject의 value 프로퍼티도 업데이트된다는 사실!입니다. 따라서 CurrentValueSubject에는 최신 값만 유지하고 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 만약에 subscription을 취소하면 어떻게 될까요? 뭐 당연하게도 값이 전달되지 않겠죠?&lt;/p&gt;
&lt;pre id=&quot;code_1641223253748&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let currentValueSubject = CurrentValueSubject&amp;lt;String, Never&amp;gt;(&quot;Pingu 첫번째 값&quot;)

let firstSubscription = currentValueSubject
    .sink(receiveCompletion: { print(&quot;1 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;1 번째 sink value: \($0)&quot;) })

let secondSubscription = currentValueSubject
    .sink(receiveCompletion: { print(&quot;2 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;2 번째 sink value: \($0)&quot;) })
    .cancel()


// 현재 Subscriber들에게 모두 보냄
currentValueSubject.send(&quot;Pingu 두번째 값&quot;)
currentValueSubject.send(completion: .finished)

print(currentValueSubject.value)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 2번째 secondSubscription은 cancel()을 사용해서 취소해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 결과는 예상대로..&lt;/p&gt;
&lt;pre id=&quot;code_1641223282817&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1 번째 sink value: Pingu 첫번째 값
2 번째 sink value: Pingu 첫번째 값
1 번째 sink value: Pingu 두번째 값
1 번째 sink completion: finished
Pingu 두번째 값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 2번째 sink에서 만들어진 Subscription에는 두 번째 값이 전달되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 여기서 completion은 어떤 역할을 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 subscription을 만들고 하나는 먼저 finished completion을 보내보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641485906094&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let currentValueSubject = CurrentValueSubject&amp;lt;String, Never&amp;gt;(&quot;Pingu 첫번째 값&quot;)

let firstSubscription = currentValueSubject
    .sink(receiveCompletion: { print(&quot;1 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;1 번째 sink value: \($0)&quot;) })

currentValueSubject.send(completion: .finished)

let secondSubscription = currentValueSubject
    .sink(receiveCompletion: { print(&quot;2 번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;2 번째 sink value: \($0)&quot;) })


// 현재 Subscriber들에게 모두 보냄
currentValueSubject.send(&quot;Pingu 두번째 값&quot;)

print(currentValueSubject.value)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 코드를 작성하면 Subscription들에게 &quot;Pingu 두 번째 값&quot;이 전달될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해서 결과를 보면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641485963882&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;========CurrentValueSubject========
1 번째 sink value: Pingu 첫번째 값
1 번째 sink completion: finished
2 번째 sink completion: finished
Pingu 첫번째 값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 finished를 전달한 뒤에 send를 통해 보내지는 값들은 모두 무시되며 CurrentValueSubject의 value도 업데이트되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 2번째 subscription에도 finished가 전달되는 것도 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PassthroughSubject&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으로 PassthroughSubject에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PassthroughSubject의 정의를 먼저 볼게요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzNCvL/btrpGAwvSml/kQzgpnmhkvKQYeBFnjuCPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzNCvL/btrpGAwvSml/kQzgpnmhkvKQYeBFnjuCPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzNCvL/btrpGAwvSml/kQzgpnmhkvKQYeBFnjuCPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzNCvL%2FbtrpGAwvSml%2FkQzgpnmhkvKQYeBFnjuCPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;950&quot; height=&quot;438&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보니 &quot;downstream의 subscriber들에게 값을 전파한다&quot;라고 되어있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아까 알아본 CurrentValuSubject와 다르게 생성할 때 딱히 초기값이 필요하지 않다고 합니다.&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;따라서 만약에 subscriber가 없거나 Demand가 0이라면 값을 보내더라도 아무 일도 발생하지 않게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현을 보면 딱 Subject 프로토콜에 필요한 메서드들만 정의되어있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641485711491&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
final public class PassthroughSubject&amp;lt;Output, Failure&amp;gt; : Subject where Failure : Error {

    public init()
    
    final public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
    
    final public func send(subscription: Subscription)
    final public func send(_ input: Output)
    final public func send(completion: Subscribers.Completion&amp;lt;Failure&amp;gt;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 CurrentValueSubject와 사용법은 동일하며, 최신 값을 저장하는 프로퍼티가 없어서 최신 값에 접근하지 못합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641487937814&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let passthroughSubject = PassthroughSubject&amp;lt;String, Never&amp;gt;()

let firstSubscription = passthroughSubject
    .sink(receiveCompletion: { print(&quot;1번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;1번째 sink value: \($0)&quot;)}
    )

passthroughSubject.send(&quot;PassthroughSubject 1번째 값&quot;)

let secondSubscription = passthroughSubject
    .sink(receiveCompletion: { print(&quot;2번째 sink completion: \($0)&quot;) },
          receiveValue: { print(&quot;2번째 sink value: \($0)&quot;)}
    )

passthroughSubject.send(&quot;PassthroughSubject 2번째 값&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 간단하게 사용해볼 수 있는데요, 아까와 마찬가지로 second Subbscription은 생성되기 전에 내보내진 값인 &quot;PassthroughSubject 1번째 값&quot;은 받지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝입니다.  최신 값을 저장하지 못한다는 것 외에는 다른 게 없어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Subject 프로토콜과 Apple에서 미리 구현해둔 Subject에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subject는 Combine을 사용하지 않던 코드에서 Combine을 사용하고자 할 때 유용하며, 미리 구현된 Subject에는 CurrentValueSubject, PassthroughSubject가 있고 두 개의 차이점은 최신 값의 저장 유무였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 구현된 Publisher들에 비해 개수가 적고 단순한 거 같네요.&lt;/p&gt;
&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://icksw.tistory.com/275&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Subscriber 사용을 위해 미리 정의된 것들에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/Subject.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>CurrentValueSubject</category>
      <category>IOS</category>
      <category>PassthroughSubject</category>
      <category>Publisher</category>
      <category>subject</category>
      <category>subscriber</category>
      <category>Subscription</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/274</guid>
      <comments>https://icksw.tistory.com/274#entry274comment</comments>
      <pubDate>Fri, 7 Jan 2022 02:08:15 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] 미리 정의된 Publisher들 - Combine 공부 3</title>
      <link>https://icksw.tistory.com/273</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 Publisher, Subscriber 프로토콜 그 자체에 대해 알아봤었는데요, 이번 글에서는 Publisher 프로토콜로 Apple에서 미리 구현한 Publisher들을 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 Publisher가 뭔지 짚어보면, Subscription을 만들고 Subscriber에게 값과 completion event를 내보내는 타입을 위한 프로토콜이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Apple에서 미리 구현한 Publisher들은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Just&lt;/li&gt;
&lt;li&gt;Future&lt;/li&gt;
&lt;li&gt;Deferred&lt;/li&gt;
&lt;li&gt;Empty&lt;/li&gt;
&lt;li&gt;Fail&lt;/li&gt;
&lt;li&gt;Record&lt;/li&gt;
&lt;li&gt;AnyPublisher&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나씩 차례대로 알아보겠습니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Just (Struct)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPh1c1/btrpvYQRunf/GEI6QlvC4cwuULyFKEWAi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPh1c1/btrpvYQRunf/GEI6QlvC4cwuULyFKEWAi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPh1c1/btrpvYQRunf/GEI6QlvC4cwuULyFKEWAi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPh1c1%2FbtrpvYQRunf%2FGEI6QlvC4cwuULyFKEWAi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;392&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 Publisher로 자신을 subscribe 하는 Subscriber들에게 한 번에 값을 내보낸 뒤 finish 이벤트를 보내는 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법도 간단합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640965589689&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let just = Just(&quot;This is Output&quot;)
just
    .sink(
        receiveCompletion: { completion in
            print(&quot;received completion: \(completion)&quot;)
        },
        receiveValue: { value in
            print(&quot;received value: \(value)&quot;)
        })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 결과는 아래와 같이 나옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640965603323&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;received value: This is Output
received completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의된 대로 자신을 subscribe 하는 Subscriber에게 값을 내보낸 뒤에 finish 이벤트를 보내네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 한 번에 보내고 finish 이벤트를 보내는 것이 Just의 특징이라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Publisher 프로토콜은 Output, Failure 프로퍼티가 필요하다고 했었는데 Just에는 왜 따로 Failure타입을 설정하지 않았을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 Just의 구현을 보면 쉽게 이해가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640966445591&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Just&amp;lt;Output&amp;gt; : Publisher {
    public typealias Failure = Never
    public let output: Output
    
    public init(_ output: Output)
    public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, S : Subscriber, S.Failure == Just&amp;lt;Output&amp;gt;.Failure
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 위와 같이 Failure 타입이 Never로 설정되어있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Never는 뭐죠?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DXTUa/btrppAw36DI/jiz212HdlnNHofLK8zOOc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DXTUa/btrppAw36DI/jiz212HdlnNHofLK8zOOc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DXTUa/btrppAw36DI/jiz212HdlnNHofLK8zOOc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDXTUa%2FbtrppAw36DI%2Fjiz212HdlnNHofLK8zOOc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;450&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의는 &quot;정상적으로 리턴하지 않는 함수의 리턴 타입, 값이 없는 타입&quot;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine에서는 Publisher가 오류를 생성하지 않는 경우 Never 타입으로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Failure타입이 Never일 때만 사용할 수 있는 Operator도 있는데 이건 나중에 정리해보도록 하겠습니다.&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;Future (Class)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 알아볼 것은 Future입니다. 정의부터 봐야겠죠?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ez2k3/btrpozrIp4D/MDaRXY2ulWvV1IK7FoCAJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ez2k3/btrpozrIp4D/MDaRXY2ulWvV1IK7FoCAJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ez2k3/btrpozrIp4D/MDaRXY2ulWvV1IK7FoCAJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEz2k3%2FbtrpozrIp4D%2FMDaRXY2ulWvV1IK7FoCAJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;200&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future는 하나의 결과를 비동기로 생성한 뒤 completion event를 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 하나의 결과를 비동기로 생성한 뒤에 subscribe되는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future의 구현을 보면 비동기로 처리된다는 게 느껴집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641093678229&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
final public class Future&amp;lt;Output, Failure&amp;gt; : Publisher where Failure : Error {
    public typealias Promise = (Result&amp;lt;Output, Failure&amp;gt;) -&amp;gt; Void
	
    public init(_ attemptToFulfill: @escaping (@escaping Future&amp;lt;Output, Failure&amp;gt;.Promise) -&amp;gt; Void)
    
    final public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promise는 Future가 값을 내보낼 때 호출되는 클로저입니다.&lt;/li&gt;
&lt;li&gt;init에서 Promise 클로저를 매개변수로 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Future는 생성할 때 값을 내보낼 때 호출할 클로저를 매개변수로 받아서 값을 한 번 내보내면 해당 값을 계속 내보내는 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 구현이 Class인 것도 확인할 수 있습니다. 이번 글에서 배울 다른 Publisher들이 모두 Struct인데 얘만 Class인 이유가 궁금해서 좀 찾아보니 비동기로 작동할 때 상태 저장 동작을 가능하게 하기 위해 Class로 구현되었다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 간단히 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1641094592002&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var subscriptions = Set&amp;lt;AnyCancellable&amp;gt;()
var emitValue: Int = 0
var delay: TimeInterval = 3

func createFuture() -&amp;gt; Future&amp;lt;Int, Never&amp;gt; {
    return Future&amp;lt;Int, Never&amp;gt; { promise in
        delay -= 1
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            emitValue += 1
            promise(.success(emitValue))
        }
    }
}

let firstFuture = createFuture()
let secondFuture = createFuture()
let thirdFuture = createFuture()

firstFuture
    .sink(receiveCompletion: { print(&quot;첫번째 Future Completion: \($0)&quot;) },
          receiveValue: { print(&quot;첫번째 Future value: \($0)&quot;) })
    .store(in: &amp;amp;subscriptions)

secondFuture
    .sink(receiveCompletion: { print(&quot;두번째 Future completion: \($0)&quot;) },
          receiveValue: { print(&quot;두번째 Future value: \($0)&quot;) })
    .store(in: &amp;amp;subscriptions)

thirdFuture
    .sink(receiveCompletion: { print(&quot;세번째 Future completion: \($0)&quot;) },
          receiveValue: { print(&quot;세번째 Future value: \($0)&quot;) })
    .store(in: &amp;amp;subscriptions)

thirdFuture
    .sink(receiveCompletion: { print(&quot;세번째 Future completion2: \($0)&quot;) },
          receiveValue: { print(&quot;세번째 Future value2: \($0)&quot;) })
    .store(in: &amp;amp;subscriptions)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 코드를 만들면 결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1641094641874&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;세번째 Future value2: 1
세번째 Future completion2: finished
세번째 Future value: 1
세번째 Future completion: finished
두번째 Future value: 2
두번째 Future completion: finished
첫번째 Future value: 3
첫번째 Future Completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 Future가 가장 먼저 완료되고, 두 번째, 첫 번째 순으로 완료되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 결과가 나오는 이유는 sink로 만든 각각의 Future에서 delay를 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;첫번째 Future는 3초 대기를 하다가 Promise 클로저가 호출되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 Future는 1초 줄어든 2초 대기를 하다가 Promise 클로저가 호출되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 Future는 1초가 더 줄어든 1초 대기를 하다가 Promise 클로저가 호출되어 위와 같은 결과가 발생한 거죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 세 번째 Future에는 두 번의 subscribe를 했는데요, 결과가 똑같이 나오는 것을 볼 수 있습니다. 즉 한 번 방출된 값을 계속해서 사용하는 것도 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 비동기로 결과를 처리할 수 있는 게 Future입니다.&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;Deferred (Struct)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vUJJ4/btrppARmfFe/7UacVGmEwPwn6qKF9Z1jk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vUJJ4/btrppARmfFe/7UacVGmEwPwn6qKF9Z1jk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vUJJ4/btrppARmfFe/7UacVGmEwPwn6qKF9Z1jk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvUJJ4%2FbtrppARmfFe%2F7UacVGmEwPwn6qKF9Z1jk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;270&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deferred는 새로운 Subscriber의 Publisher를 만들기 위해 제공된 클로저를 실행하기 전에 Subscription을 기다리는 Publisher라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deferred가 번역하면 &quot;지연된&quot;이라는 의미인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 Deferred의 구현을 보면서 알아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641094996996&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Deferred&amp;lt;DeferredPublisher&amp;gt; : Publisher where DeferredPublisher : Publisher {

    public typealias Output = DeferredPublisher.Output
    public typealias Failure = DeferredPublisher.Failure
    
    public let createPublisher: () -&amp;gt; DeferredPublisher
    public init(createPublisher: @escaping () -&amp;gt; DeferredPublisher)
    
    public func receive&amp;lt;S&amp;gt;(subscriber: S) where S : Subscriber, DeferredPublisher.Failure == S.Failure, DeferredPublisher.Output == S.Input
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 다른 것들은 모두 동일한데 createPublisher 클로저와 생성자가 좀 다르네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;createPublisher는 Publisher가 subscribe 됐을 때 실행할 클로저입니다.&lt;/li&gt;
&lt;li&gt;init에서 받은 클로저는 subscribe(_:)가 호출될 때 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 사용해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641097557460&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PinguPublisher: Publisher {
    typealias Output = String
    typealias Failure = Never
    
    func receive&amp;lt;S&amp;gt;(subscriber: S) where S : Subscriber, Never == S.Failure, String == S.Input {
        subscriber.receive(&quot;안녕 나는 pinguPublisher&quot;)
        subscriber.receive(completion: .finished)
    }
}

print(&quot;deferred publisher가 만들어짐&quot;)
let deferred = Deferred { () -&amp;gt; PinguPublisher in
    print(&quot;pinguPublisher가 만들어짐\n&quot;)
    return PinguPublisher()
}

deferred
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1641097571062&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;deferred publisher가 만들어짐
pinguPublisher가 만들어짐

안녕 나는 pinguPublisher
finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 Deferred가 만들어졌을 때 PinguPublisher는 만들어지지 않았고, 이후에 sink를 통해 subscribe 했을 때 Deferred를 생성할 때 구현한 클로저에서 만들어지는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 lazy와 비슷하게 Publisher가 실제로 사용될 때 Publisher를 생성해서 사용해서 메모리를 효율적으로 사용할 수 있어 보입니다.&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;AnyPublisher&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 Publisher들을 알아보려면 AnyPublisher를 알아야 될 거 같아서 지금 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHd7g3/btrpthR8HuH/ptGDTRyZTglZOPI20fm4rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHd7g3/btrpthR8HuH/ptGDTRyZTglZOPI20fm4rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHd7g3/btrpthR8HuH/ptGDTRyZTglZOPI20fm4rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHd7g3%2FbtrpthR8HuH%2FptGDTRyZTglZOPI20fm4rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;224&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 읽어보면 AnyPublisher는 자체적으로 뭐 중요한 건 없고 Upstream Publisher의 값들을 전달하는 Publisher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 Publisher에서도 EraseToAnyPublisher()를 호출하면 AnyPublisher로 래핑 됩니다.&lt;/p&gt;
&lt;p 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_1641099736692&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let originalPublisher = [1, nil, 3].publisher

let anyPublisher = originalPublisher.eraseToAnyPublisher()
anyPublisher.sink { value in
    print(value)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 하면 originalPublisher의 타입은&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;962&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KATmI/btrpu5XWzuA/nwb2krgdScWvnACzPooNPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KATmI/btrpu5XWzuA/nwb2krgdScWvnACzPooNPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KATmI/btrpu5XWzuA/nwb2krgdScWvnACzPooNPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKATmI%2Fbtrpu5XWzuA%2Fnwb2krgdScWvnACzPooNPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;126&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되는데, 이걸 eraseToAnyPublisher로 AnyPublisher로 래핑 한 anyPublisher의 타입은&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWLQAd/btrpD0nh5Sv/M4uK0AtxiCQgz5kn7slcEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWLQAd/btrpD0nh5Sv/M4uK0AtxiCQgz5kn7slcEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWLQAd/btrpD0nh5Sv/M4uK0AtxiCQgz5kn7slcEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWLQAd%2FbtrpD0nh5Sv%2FM4uK0AtxiCQgz5kn7slcEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;152&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;152&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;이러한 AnyPublisher는 Combine을 사용하다 보면 여러 Operator를 사용하면서 여러 Publisher 타입이 생성될 수 있는데 이걸 간단하게 처리하기 위해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 복잡해지는지는 Empty에서 알아보도록 하겠습니다.&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;Empty (Struct)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctdfly/btrpxw75klm/mfk0nL2fP5tX44EfHD5TRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctdfly/btrpxw75klm/mfk0nL2fP5tX44EfHD5TRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctdfly/btrpxw75klm/mfk0nL2fP5tX44EfHD5TRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fctdfly%2Fbtrpxw75klm%2Fmfk0nL2fP5tX44EfHD5TRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;330&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 Empty라는 단어에서 느껴지듯이 Empty는 아무런 값도 내보내지 않고 즉시 completion 이벤트를 보낼지 선택할 수 있는 Publisher입니다.&lt;/p&gt;
&lt;p 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_1641097905905&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Empty&amp;lt;Output, Failure&amp;gt; : Publisher, Equatable where Failure : Error {
    public init(completeImmediately: Bool = true)
    public init(completeImmediately: Bool = true, outputType: Output.Type, failureType: Failure.Type)
    public let completeImmediately: Bool
    
    public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;init이 두 개니 차이점을 살펴보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;init(completeImmediately: Bool = true)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;completion 이벤트를 바로 보낼지만 결정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;init(completeImmediately: Bool = true, outputType: Output.Type, failureType: Failure.Type)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Empty를 Subscriber나 다른 Publisher에 연결할 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;completeImmediately는 Empty가 즉시 completion 되어야 하는지 여부를 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 간단하게 사용해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1641098184847&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let empty = Empty&amp;lt;String, Never&amp;gt;()
empty
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) }
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 예상대로 그냥 completion만 출력됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641098212365&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;completion: finished&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;근데 이렇게 말고 Subscriber나 다른 Publisher에 연결할 때 사용할 수도 있다고 했으니 그렇게도 한 번 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 AnyPublisher도 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641099978031&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let anyPublisher = [1, nil, 3].publisher
    .flatMap { value -&amp;gt; AnyPublisher&amp;lt;Int, Never&amp;gt; in
        if let value = value {
            return Just(value).eraseToAnyPublisher()
        } else {
            return Empty().eraseToAnyPublisher()
        }
    }.eraseToAnyPublisher()


anyPublisher.sink(receiveCompletion: { print(&quot;AnyPublisher completion: \($0)&quot;) },
                  receiveValue: { print(&quot;value: \($0)&quot;) }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 뭔가 조건에 맞게 Publisher를 처리하고 싶을 때 Empty를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과는 아래와 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1641100097873&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;value: 1
value: 3
AnyPublisher completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 nil을 거르고 싶은 코드라는 것을 알 수 있는데요, 여기서 값이 nil인 경우에는 Empty로 처리해서 DownStream에 보냅니다. 이렇게 하면 값을 빈 상태로 처리할 수 있어요. 따라서 결과에서도 nil은 안 나오고 1, 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;그리고 AnyPublisher를 공부할 때 Combine을 사용하다 보면 여러 타입의 Publisher를 처리하게 된다고 했는데 위에서도 Just, Empty의 두 가지 타입을 DownStream에 내려줘야 하는 상황이 생겼습니다. 이를 직접 처리하기보다는 AnyPublisher로 래핑 해서 DownStream으로 보내면 쉽게 처리할 수 있습니다.&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;Fail (Struct)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bagGUg/btrpvSc22Ly/MHwlormHudxrOMtevPXyk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bagGUg/btrpvSc22Ly/MHwlormHudxrOMtevPXyk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bagGUg/btrpvSc22Ly/MHwlormHudxrOMtevPXyk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbagGUg%2FbtrpvSc22Ly%2FMHwlormHudxrOMtevPXyk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;210&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 Empty가 completion 이벤트를 즉시 보낼 수 있었다면 Fail은 Error를 즉시 보낼 수 있는 Publisher입니다.&lt;/p&gt;
&lt;p 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_1641100535368&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Fail&amp;lt;Output, Failure&amp;gt; : Publisher where Failure : Error {
    public init(error: Failure)
    public init(outputType: Output.Type, failure: Failure)

    public let error: Failure
    public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 즉시 보낼 error를 가지고 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p 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_1641100670538&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PinguError: Error {
    case itIsNil
}

let fail = Fail&amp;lt;String, PinguError&amp;gt;(error: .itIsNil)

fail.sink(receiveCompletion: { print(&quot;receive completion: \($0)&quot;) },
          receiveValue: { print(&quot;receive value: \($0)&quot;) }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 PinguError라는 에러 타입을 만들어서 Fail이 즉시 내보낼 에러로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면..&lt;/p&gt;
&lt;pre id=&quot;code_1641100727824&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;receive completion: failure(__lldb_expr_92.PinguError.itIsNil)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별다른 값은 보내지 않고 에러만 받은 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fail도 아까 Empty와 비슷하게 활용할 수 있는데요!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 예제와 비슷한 예제를 구현해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641100977530&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let anyPublisher = [1, nil, 3].publisher
    .flatMap { value -&amp;gt; AnyPublisher&amp;lt;Int, PinguError&amp;gt; in
        if let value = value {
            let just = Just(value).setFailureType(to: PinguError.self)
            return just.eraseToAnyPublisher()
        } else {
            return Fail&amp;lt;Int, PinguError&amp;gt;(error: .itIsNil).eraseToAnyPublisher()
        }
    }
    .sink(receiveCompletion: { print(&quot;Completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) }
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와 다르게 nil이 발견되면 Fail을 반환해서 처리하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fail은 Empty와 다르게 에러를 내보내기 때문에 결과도 아래와 같이 나오게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641101040384&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;value: 1
Completion: failure(__lldb_expr_101.PinguError.itIsNil)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Record (Struct)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Record를 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ec3DXF/btrpBC1i0j7/z6mcNefqehL8YOJly0OKxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ec3DXF/btrpBC1i0j7/z6mcNefqehL8YOJly0OKxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ec3DXF/btrpBC1i0j7/z6mcNefqehL8YOJly0OKxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fec3DXF%2FbtrpBC1i0j7%2Fz6mcNefqehL8YOJly0OKxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;228&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 각각의 Publisher가 나중에 내보낼 수 있도록 input과 completion을 저장해두는 Publisher라고 합니다.&lt;/p&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과 completion을 저장해둬야 하니까 구현에서도 그런 프로퍼티가 있을 거 같은데, 정말 그럴지 확인해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641101265674&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Record&amp;lt;Output, Failure&amp;gt; : Publisher where Failure : Error {
    public let recording: Record&amp;lt;Output, Failure&amp;gt;.Recording
    public init(record: (inout Record&amp;lt;Output, Failure&amp;gt;.Recording) -&amp;gt; Void)
        
    public init(recording: Record&amp;lt;Output, Failure&amp;gt;.Recording)
    public init(output: [Output], completion: Subscribers.Completion&amp;lt;Failure&amp;gt;)
    public func receive&amp;lt;S&amp;gt;(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
                    
	
    public struct Recording {
        public typealias Input = Output
        public var output: [Output] { get }
        public var completion: Subscribers.Completion&amp;lt;Failure&amp;gt; { get }
        public init()
        public init(output: [Output], completion: Subscribers.Completion&amp;lt;Failure&amp;gt; = .finished
        public mutating func receive(_ input: Record&amp;lt;Output, Failure&amp;gt;.Recording.Input)
        public mutating func receive(completion: Subscribers.Completion&amp;lt;Failure&amp;gt;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 값들을 저장하기 위해서 Recording이라는 구조체가 내부에 구현된 것을 볼 수 있네요.&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_1641101709802&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let record = Record&amp;lt;String, Never&amp;gt;(output: [&quot;Pingu&quot;, &quot;Pinga&quot;, &quot;Roby&quot;], completion: .finished)

record
    .sink(receiveCompletion: { print(&quot;completion: \($0)&quot;) },
          receiveValue: { print(&quot;value: \($0)&quot;) }
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 간단하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과도 간단해요.&lt;/p&gt;
&lt;pre id=&quot;code_1641101732551&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;value: Pingu
value: Pinga
value: Roby
completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 이후에도 값을 넣을 수 있을지 궁금해서 좀 찾아봤더니 Record의 값들을 저장하는 Recording 구조체의 구현에 있는 receive() 메서드들에는 아래와 같은 특징이 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;receive(_ input: Record&amp;lt;Output, Failure&amp;gt;.Recording.Input)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;completion이 추가된 이후에 값을 추가하려고 하면 Fatal Error 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(completion: Subscribers.Completion&amp;lt;Failure&amp;gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;completion는 하나만 추가 가능, 추가로 호출할 시 Fatal Error 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넵.. 안된다는 걸로;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Apple에서 미리 구현해둔 Publisher들을 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것 외에도 직접 Publisher를 구현할 수도 있으니 필요한 건 직접 구현해서 사용하면 될 것 같아요.&lt;/p&gt;
&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://icksw.tistory.com/274&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 Subject에 대해 알압보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/iOS_Study/blob/master/Combine/PreDefinedPublisher.playground/Contents.swift&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;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>deferred</category>
      <category>Empty</category>
      <category>Fail</category>
      <category>Future</category>
      <category>Just</category>
      <category>Publisher</category>
      <category>record</category>
      <category>subscriber</category>
      <category>Subscription</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/273</guid>
      <comments>https://icksw.tistory.com/273#entry273comment</comments>
      <pubDate>Sun, 2 Jan 2022 14:43:23 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Publisher &amp;amp; Subscriber - Combine 공부 2</title>
      <link>https://icksw.tistory.com/272</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/271&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 간단하게 Combine이 뭔지에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 다시 요약해보면 Apple에서 2019년에 만든 새로운 프레임워크인데, 이걸 쓰면 비동기 이벤트를 간단하게 처리할 수 있다! 정도?입니다. (Apple에서 만든 RxSwift라고 봐도 됩니다. )&lt;/p&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에는 Publisher, Subscriber, Subscription, Operator가 있는데, Operator는 종류가 너무 많으니 이번 글에서는 Publisher, Subscriber, Subscription에 대해 알아보려고 합니다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부를 하다보니 일단 Publisher, Subscriber, Subscription이 뭔지 알기 전에 이것들이 어떻게 동작되는지 흐름을 알고 가면 좋을 거 같더라고요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_B9F7ABCEC66A-1.jpeg&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s8x5k/btrpcPAo20y/nU8ktDLjiTzkPP9p70kNc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s8x5k/btrpcPAo20y/nU8ktDLjiTzkPP9p70kNc0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s8x5k/btrpcPAo20y/nU8ktDLjiTzkPP9p70kNc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs8x5k%2FbtrpcPAo20y%2FnU8ktDLjiTzkPP9p70kNc0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;440&quot; data-filename=&quot;IMG_B9F7ABCEC66A-1.jpeg&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같이 간단하게 흐름도를 만들어 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예제도 한 번 볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1640704207014&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class IntSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    
    // Publisher가 Subscription주면 호출됨
    func receive(subscription: Subscription) {
        subscription.request(.max(1))
    }
    
    // Publisher가 주는 값을 처리
    func receive(_ input: Int) -&amp;gt; Subscribers.Demand {
        print(&quot;Received value: \(input)&quot;)
        // Publisher에게 한 번 더 달라고 요청
        return .max(1)
    }
    
    func receive(completion: Subscribers.Completion&amp;lt;Never&amp;gt;) {
        print(&quot;Received completion: \(completion)&quot;)
    }
}

// IntArray를 하나 만듭니다.
let intArray: [Int] = [1,2,3,4,5]

// IntSubscriber를 만듭니다.
let intSubscriber = IntSubscriber()

// IntArray Publisher를 subscribe 합니다.
// intSubscriber가 intArray.publisher에게 값을 요청하면 달라고 말합니다.
intArray.publisher
    .subscribe(intSubscriber)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 간단하게 예제를 만들어봤는데요, 아까 본 흐름도 그림으로 이해해보면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;intArray를 하나 만들어서 그냥 존재하게 만들었습니다.&lt;/li&gt;
&lt;li&gt;intSubscriber도 하나 만듭니다.&lt;/li&gt;
&lt;li&gt;intSubscriber가 intArray.publisher에게 값을 요청하면 달라고 말합니다. subscribe(_:) 메서드 부분이에요!&lt;/li&gt;
&lt;li&gt;그럼 Publisher가 subscription을 만들어서 receive(subscription:)을 통해 Subscriber에게 줍니다.&lt;/li&gt;
&lt;li&gt;Subscriber는 Subscription을 받으면 Publisher에게 request(_:)를 통해 값을 달라고 합니다.&lt;/li&gt;
&lt;li&gt;Publisher는 값을 Subscriber에게 주다가 더 이상 줄 게 없으면 completion event를 전달합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행해보면 결과는 아래와 같이 나옵니다!&lt;/p&gt;
&lt;pre id=&quot;code_1640704799889&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Received value: 1
Received value: 2
Received value: 3
Received value: 4
Received value: 5
Received completion: finished&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠.. 흐름이 이해가 되시나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 용어가 나오면서 헷갈리실 거 같은데.. 그래도 흐름을 이해하시는데 도움이 되셨길 바라면서 하나씩 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Publisher&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine의 핵심 역할을 하는 Publisher는 Protocol입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 공부하다 보니 Publishers도 있더라고요? 헷갈리게..;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCx4b/btrpdqtra77/zqwM346u3C8qUP8dtakvN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCx4b/btrpdqtra77/zqwM346u3C8qUP8dtakvN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCx4b/btrpdqtra77/zqwM346u3C8qUP8dtakvN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCx4b%2Fbtrpdqtra77%2FzqwM346u3C8qUP8dtakvN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;184&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Publishers는 Operator들을 모아둔 Enum입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 알아볼 여러 가지 Operator들이 여기에 정의되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 다시 Publisher로 돌아가서, Publisher 프로토콜은 아래와 같이 생겼습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn2hLN/btro6aZWonl/HPhHKGExjoXpm1SWomA7O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn2hLN/btro6aZWonl/HPhHKGExjoXpm1SWomA7O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn2hLN/btro6aZWonl/HPhHKGExjoXpm1SWomA7O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn2hLN%2Fbtro6aZWonl%2FHPhHKGExjoXpm1SWomA7O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1614&quot; height=&quot;706&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 여러분이 직접 Publisher를 구현하고자 한다면 Output, Failure, receive(subscriber:)는 반드시 구현해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘네가 하는 역할을 알아보면 다음과 같아요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Output
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 생성할 수 있는 값의 타입을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Failure
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 생성할 수 있는 Error 타입을 나타냅니다.&lt;/li&gt;
&lt;li&gt;만약 Error를 생성하지 않는 Publisher를 만들고 싶다면 Never 타입으로 설정해주면 됩니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(subscriber:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher 자신을 subscribe 하는 subscriber를 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음 근데 아까 흐름 설명할 때 못 봤던 receive(subscriber:)라는 메서드가 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 예제에서는 subscribe(_:)였던 거 같은데 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscribe(_:)는 이렇게 선언되어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pPOsp/btrpaIuKI2p/imTeZagapJKx9LQhKVLjd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pPOsp/btrpaIuKI2p/imTeZagapJKx9LQhKVLjd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pPOsp/btrpaIuKI2p/imTeZagapJKx9LQhKVLjd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpPOsp%2FbtrpaIuKI2p%2FimTeZagapJKx9LQhKVLjd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1756&quot; height=&quot;394&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 보면 여기서 receive(subscriber:) 대신에 얘를 호출하라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 Subscriber가 Publisher의 subscribe메서드를 호출하면 자신을 receive(subscriber:) 메서드를 통해 Publisher에게 알리게 되는 거죠.&lt;/p&gt;
&lt;p 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;Publisher를 한 마디로 정리해보자면 &quot;자신을 Subscribe 한 Subscriber에게 값을 내보내는 프로토콜&quot; 정도가 되겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher는 프로토콜이라고 했었는데요, 그래서 애플은 자주 쓸 것 같은 Publisher들을 미리 정의해뒀는데요 걔네들은 아래와 같은 이름을 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deferred&lt;/li&gt;
&lt;li&gt;Empty&lt;/li&gt;
&lt;li&gt;Fail&lt;/li&gt;
&lt;li&gt;Future&lt;/li&gt;
&lt;li&gt;Just&lt;/li&gt;
&lt;li&gt;Record&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들에 대해서는 &lt;a href=&quot;https://icksw.tistory.com/273&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;이 외에도 기존의 명령형 코드(Combine은 선언적 코드)에서 Combine Subscriber에게 값을 보내는 중개자 역할을 수행하는 Subject도 있는데요, 이런 것들은 &lt;a href=&quot;https://icksw.tistory.com/274&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;그럼 다음으로는 Subscriber를 알아보죠~!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Subscriber&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber도 역시 Protocol입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAaoYP/btrpcOvEsN9/M5yrqGS6A4hMCfnqiHZsR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAaoYP/btrpcOvEsN9/M5yrqGS6A4hMCfnqiHZsR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAaoYP/btrpcOvEsN9/M5yrqGS6A4hMCfnqiHZsR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAaoYP%2FbtrpcOvEsN9%2FM5yrqGS6A4hMCfnqiHZsR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1720&quot; height=&quot;972&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 위에서 제가 구현한 간단한 예제에서 IntSubscriber를 만들었었는데, 그때처럼 직접 구현하려고 한다면 아래의 것들은 필수적으로 구현해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Input
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher에게 받는 값의 타입입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Failure
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher에게 받는 Error 타입입니다.&lt;/li&gt;
&lt;li&gt;만약 Error를 수신하지 않고 싶다면 Never 타입으로 설정해주면 됩니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(subscription:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 만들어서 주는 subscription을 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(input:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 주는 값을 받습니다.&lt;/li&gt;
&lt;li&gt;Demand를 반환하는데 이는 값을 더 원하는지에 대한 여부입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;receive(completion:)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher가 주는 completion event를 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 Publisher에서는 값의 타입 이름이 Output이었고 Subscriber에서는 Input이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine을 사용할 때 주의할 점은 Publisher의 &amp;lt;Output, Failure&amp;gt; 타입과 Subscriber의 &amp;lt;Input, Failure&amp;gt; 타입이 동일해야 한다는 것!입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 다르면 Publisher와 Subscriber는 서로 값을 주고받지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 receive(input:)의 반환 타입이 Subscribers.Demand인 거 보이시나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publishers에 이어서 또 헷갈리게 Subscribers도 있네요..ㅡㅡ&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;480&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7C2Cf/btrpffsimv4/YLNKqQVy8Y7IXYYkEPwHMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7C2Cf/btrpffsimv4/YLNKqQVy8Y7IXYYkEPwHMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7C2Cf/btrpffsimv4/YLNKqQVy8Y7IXYYkEPwHMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7C2Cf%2Fbtrpffsimv4%2FYLNKqQVy8Y7IXYYkEPwHMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;107&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보니 Subscriber 역할을 하는 타입들을 정의해둔 곳이라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 뭐가 있는지 보면 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Demand&lt;/li&gt;
&lt;li&gt;Completion&lt;/li&gt;
&lt;li&gt;Sink&lt;/li&gt;
&lt;li&gt;Assign&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들도 &lt;a href=&quot;https://icksw.tistory.com/275&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서 자세히 알아보도록 하고 지금은 Subscriber 프로토콜에 있던 Demand만 간단히 살펴보고 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBBN25/btrpeJgiUHl/tDQljsmKkgbUikJcviCqtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBBN25/btrpeJgiUHl/tDQljsmKkgbUikJcviCqtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBBN25/btrpeJgiUHl/tDQljsmKkgbUikJcviCqtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBBN25%2FbtrpeJgiUHl%2FtDQljsmKkgbUikJcviCqtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;117&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의를 보면 Subscription을 통해 Subscriber가 Publisher에게 보낸 request 횟수라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Subscriber가 Publisher에게 값을 달라고 요청할 수 있는데 그런 요청을 한 횟수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 아까 receive(input:)의 반환 타입이 Demand인데요, 이는 Publisher에게 몇 번 더 값을 달라고 요청할지에 대한 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 간단히 구현한 IntSubscriber의 receive(input:)을 다시 볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1640792907008&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Publisher가 주는 값을 처리
func receive(_ input: Int) -&amp;gt; Subscribers.Demand {
	print(&quot;Received value: \(input)&quot;)
	// Publisher에게 한 번 더 달라고 요청
	return .max(1)
    
	// Publisher에게 값 더 이상 안줘도 된다고 알림
	// return .none
	// Publisher에게 끝없이 값을 달라고 요청
	// return .unlimited
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이. max(1)을 반환하면 한 번 더 달라고 요청하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도. none,. unlimited가 있는데요 이에 대한 설명은 주석으로 달아뒀으니 참고해주세요~&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Subscription&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 마지막으로 Publisher가 만들어서 Subscriber에게 준다고 했던 Subscription에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Subscription 역시 Protocol입니다.&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;956&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P5c6P/btrpeJ2paHs/fUIJ8wRvcY32NkkJgPHIJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P5c6P/btrpeJ2paHs/fUIJ8wRvcY32NkkJgPHIJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P5c6P/btrpeJ2paHs/fUIJ8wRvcY32NkkJgPHIJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP5c6P%2FbtrpeJ2paHs%2FfUIJ8wRvcY32NkkJgPHIJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;478&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Publisher와 Subscriber를 연결하는 프로토콜&quot;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 어떤 걸 요구하는지 보면 아래와 같아요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjz793/btrpmhpgTRb/rNa3J3PtKuZB2pAfyo2c4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjz793/btrpmhpgTRb/rNa3J3PtKuZB2pAfyo2c4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjz793/btrpmhpgTRb/rNa3J3PtKuZB2pAfyo2c4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjz793%2FbtrpmhpgTRb%2FrNa3J3PtKuZB2pAfyo2c4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1244&quot; height=&quot;214&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher나 Subscriber에 비해 아주 간단하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱 request(demand:)만 필요로 합니다.&lt;/p&gt;
&lt;p 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;아까 예제에서 Subscriber의 receive(subscription:) 메서드를 다시 볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1640793516954&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Publisher가 Subscription주면 호출됨
func receive(subscription: Subscription) {
    // Subscription에게 값을 1번 요청
    subscription.request(.max(1))
}&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;Publisher에게 받은 Subscription의 request(demand:)를 호출해서 Publisher에게 값을 요청합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Demand는 아까 말한 대로 Publisher에게 값을 몇 번 달라고 요청하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Subscriber가 Publisher에게 값을 요청할 때 Subscription을 사용한다고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이해하면 Subscription의 정의가 잘 이해가 되는 듯하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 공식문서를 보면, Subscription은 특정 Subscriber가 Publisher에 연결된 순간 설정되는 Identity가 있어서 한 번만 cancel 할 수 있고 cancel 하면 이전에 연결된 Subscriber들이 모두 해제합니다. 이런 과정은 스레드로부터 안전해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel을 할 수 있는 이유는 Subscription 프로토콜이 Cancellable 프로토콜을 채택했기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Cancellable 프로토콜을 보면 아래와 같이 생겼습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640873156635&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public protocol Cancellable {
    func cancel()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Subscription에 명시적으로 cancel()을 호출하지 않으면 Publisher가 complete 될 때까지나 메모리에서 subscription이 해제될 때까지 Subscription이 계속됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플에서는 Cancellable을 채택한 AnyCancellable 클래스도 구현해뒀습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640874379425&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final public class AnyCancellable: Cancellable, Hashable {
    public init(_ cancel: @escaping () -&amp;gt; Void)
    
    public init&amp;lt;C&amp;gt;(_ canceller: C) Where C: Cancellable
    
    final public func cancel()
}&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;이렇게 AnyCancellable은 cancel()이 호출되면 실행될 closure를 설정할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber를 구현할 때 Anycancellable을 사용해서 publisher를 cancel 할 수 있지만 Subscription 객체를 사용해서 item을 요청할 수 없는 cancellation token을 제공할 수도 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 AnyCancellable은 메모리에서 해제될 때 자동으로 cancel()을 호출합니다.&lt;/p&gt;
&lt;p 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_1640874938475&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AnyCancellable
let subject = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let anyCancellable = subject
    .sink(receiveCompletion: { completion in
        print(&quot;received completion: \(completion)&quot;)
    }, receiveValue: { value in
        print(&quot;received value: \(value)&quot;)
    })

subject.send(1)
anyCancellable.cancel()
subject.send(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 1은 전달되지만 2는 이미 cancel()이 호출된 후라서 전달되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;근데 이렇게 cancel()할 때 실행될 closure도 구현할 수 있다고 했는데요 이건 아래와 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640876377973&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let subject = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let anyCancellable = subject
    .handleEvents(
        receiveCancel: {
            // cancel()이 불렸을 때 호출될 클로저
            print(&quot;Cancel 불렸음!&quot;)
        })
    .sink(receiveValue: { value  in
        print(&quot;Receive Value: \(value)&quot;)
    })
subject.send(1)
anyCancellable.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleEvents는 여러 가지 이벤트가 발생했을 때 처리할 수 있는 Operator입니다. 여러가지 이벤트를 처리할 수 있지만 위 예제에서는 cancel이 호출됐을 경우에만 뭔갈 하도록 구현했어요.&lt;/p&gt;
&lt;p 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_1640876468227&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Receive Value: 1
Cancel 불렸음!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 알아보겠지만 sink는 Subscriber를 만들고 바로 request 하는 operator입니다.&lt;/p&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에서 가장 중요한 Publisher, Subscriber, Subscription에 대해서 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 정리하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher는 값이나 completion event를 Subscriber에게 전달합니다.&lt;/li&gt;
&lt;li&gt;Subsriber는 Subscription을 통해 Publisher에게 값을 요청합니다.&lt;/li&gt;
&lt;li&gt;Subscription은 Publisher와 Subscriber 사이를 연결합니다.&lt;/li&gt;
&lt;li&gt;Subscription은 cancel()을 통해 취소할 수 있으며 이때 호출될 클로저를 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정도가 되겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한 이해가 쉽게 되도록 작성해봤는데... Combine을 공부하시는 누군가에게 도움이 되셨으면 좋겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icksw.tistory.com/273&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 애플에서 3개의 프로토콜을 사용해서 미리 구현해둔 클래스나 구조체를 살펴볼 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다~!&lt;/p&gt;</description>
      <category>iOS/Combine</category>
      <category>Anycancellable</category>
      <category>Combine</category>
      <category>IOS</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>subscriber</category>
      <category>Subscription</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/272</guid>
      <comments>https://icksw.tistory.com/272#entry272comment</comments>
      <pubDate>Fri, 31 Dec 2021 00:13:22 +0900</pubDate>
    </item>
    <item>
      <title>[Combine] Combine이란? - Combine 공부 1</title>
      <link>https://icksw.tistory.com/271</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;br /&gt;&lt;br /&gt;요즘 나름 열심히 공부하는 프레임워크인 Combine를 복습 겸 확실히 공부할 겸 써보려고 하는 Combine입니다. &lt;br /&gt;공부할 것 중 우선순위를 둔 것이 Combine, SwiftUI인데, SwiftUI를 공부하려고 보니 Combine을 알아야 하더라고요?&lt;br /&gt;그래서 Combine부터 공부하고 있습니다.&lt;br /&gt;&lt;br /&gt;제가 공부하면서 많이 어려웠고 아직도 어렵지만... 누구든지 이해할 수 있는 Combine 정보글이 되도록 열심히... 써보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Combine이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Combine은 뭘까요?&lt;br /&gt;Combine은 2019년에 Apple에서 만든 새로운 프레임워크입니다. (RxSwift의 애플 버전이라고 생각하셔도 됩니다 )&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://developer.apple.com/documentation/combine&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Apple Developer Documentation&quot; data-ke-align=&quot;alignCenter&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/combine&quot; data-og-url=&quot;https://developer.apple.com/documentation/combine&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/combine&quot;&gt;
&lt;div class=&quot;og-image&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-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;애플의 공식문서에 Combine의 정의를 번역해보면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;이벤트 처리 Operator를 Combine 하여 비동기식 이벤트 처리를 Customize 합니다.&lt;/span&gt;&lt;/h4&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 되어있네요.&lt;br /&gt;&lt;br /&gt;좀 더 말을 풀어보면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;Combine 프레임워크는 시간 경과에 따라 변경되는 값을 내보내는 Publisher와 이를 수신하는 Subscriber로 시간 경과에 따른 값 처리를 위한 선언적 Swift API&lt;/span&gt;&lt;/h4&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;br /&gt;&lt;br /&gt;간단하게 말해서 Publisher, Subscriber와 같은 녀석들을 사용해서 비동기 이벤트를 처리하기 위한 프레임워크다. 정도로 이해하면 될 것 같아요.&lt;br /&gt;&lt;br /&gt;사실 비슷한 목적을 위해 이미 존재하던 RxSwift라는 게 있죠. 근데 얘는 애플에서 만든 게 아닌데 Combine은 무려 애플에서 만들었습니다. 이제 RxSwift 쓰지 말고 Combine이나 써라는 말로 들리네요.  Apple에서 만들어서 그런지 기본 API에서도 아주 쉽게 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Combine 꼭 써야 하나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 우리는 Combine이 없어도 Delegate 패턴, 콜백 함수, completion 클로저 등을 활용해서 비동기 프로그래밍을 잘 구현하고 있었습니다. 저는 이런 걸 비동기로 구현할 때 아주 깊은 콜백 함수나 delegate가 여러 개가 되는 경우 코드도 지저분해지고 예외 처리도 까다로웠는데, Combine을 사용하면 깔끔하게 처리할 수 있다고 합니다! 또한 이번에 Swift 5.5에서 새로 나온 async, await까지 함께 사용하면 엄청 깔끔하게 비동기 프로그램을 만들 수 있을 듯합니다.&lt;br /&gt;&lt;br /&gt;Combine의 유일한 단점은 iOS 13부터 사용할 수 있다는 점.. 그래서 만약 최소 버전이 낮은 앱을 개발해야 한다면 어쩔 수 없이 RxSwift와 같은 다른 선택지를 선택해야 했습니다. 하지만 언젠가 최소 버전은 오르기 마련이니 애플에서 만든 Combine은 필수적인 지식이 될 듯합니다. 특히 SwiftUI를 공부하기 위해서도 필수적으로 공부해야 해서 애플 플랫폼 개발자라면 반드시 공부해야 한다고 생각합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Combine의 구성 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 Combine이 좋다는 것도 알았고 공부해야 하는 것도 알았으니 Combine이 무엇으로 비동기 이벤트를 처리하는지 알아봐야겠죠?&lt;br /&gt;&lt;br /&gt;제가 공부하면서 느낀 Combine의 핵심은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher&lt;/li&gt;
&lt;li&gt;Subscriber&lt;/li&gt;
&lt;li&gt;Operator&lt;/li&gt;
&lt;li&gt;Subscription&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다. 사실 이거말고도 많은 개념들이 있지만 일단 저 4개가 가장 중요해 보였습니다. &lt;br /&gt;&lt;br /&gt;Combine의 여러 부분이 Protocol로 정의되어있어서 Protocol에 대한 개념도 다시 공부해보면 도움이 될 것 같았어요. 특히 저 4개는 모두 Protocol입니다.&lt;br /&gt;&lt;br /&gt;각각의 프로토콜에 대한 세부적인 부분은 이후 글에서 자세히 알아보도록 하고 이번 글에서는 간단하게 저게 뭔지 정도만 짚고 넘어가려고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher는 하나 혹은 여러 개의 Subscriber 객체에 시간이 흐름에 따라 값을 내보낼 수 있는 타입을 선언하기 위한 프로토콜입니다.&lt;/li&gt;
&lt;li&gt;Output, Failure 타입이 제네릭으로 구현되어 있습니다.&lt;/li&gt;
&lt;li&gt;간단하게 이를 사용하라고 애플에서는 자주 사용할 것 같은 기능으로 Future, Just, Deferred, Empty, Fail, Record와 같은 Publisher프로토콜을 준수하는 Struct, Class들을 구현해뒀습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Subscriber
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subscriber는 Publisher에게 값을 받을 수 있는 타입을 선언하기 위한 프로토콜입니다.&lt;/li&gt;
&lt;li&gt;Input, Failure 타입이 제네릭으로 구현되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Operator
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Operator는 Publisher를 반환하는 Publisher 프로토콜에 정의된 메서드들입니다.&lt;/li&gt;
&lt;li&gt;여러 종류의 Operator를 Combine 하여 사용하여 Publisher가 내보내는 값을 처리합니다.&lt;/li&gt;
&lt;li&gt;Upstream, DownStream이라고 하는 Input, Output을 가지고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Subscription
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subscription은 Publisher와 Subscriber의 연결을 나타내는 프로토콜입니다.&lt;/li&gt;
&lt;li&gt;간단하게 말해서 Publisher + Operator + Subscriber로 이뤄진 하나의 작업이 Subscription입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 Combine은 Apple에서 만든 짱 좋은 비동기 처리 프레임워크!라는 것입니다.&lt;br /&gt;좀 더 격렬한 소개를 보고 싶으시다면 &lt;a href=&quot;https://icksw.tistory.com/268&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;[WWDC 2019] Introducing Combine&lt;/span&gt;&lt;/a&gt;을 보시면 좋습니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://icksw.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;다음 글&lt;/span&gt;&lt;/a&gt;에서 계속해서 Combine에 대해 열심히 알아보도록 하겠습니다.&lt;br /&gt;&lt;br /&gt;감사합니다.&lt;/p&gt;</description>
      <category>iOS/Combine</category>
      <category>Combine</category>
      <category>concurrent</category>
      <category>IOS</category>
      <category>Publisher</category>
      <category>subscriber</category>
      <category>Subscription</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/271</guid>
      <comments>https://icksw.tistory.com/271#entry271comment</comments>
      <pubDate>Mon, 27 Dec 2021 00:26:37 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2020] Advances in diffable data sources</title>
      <link>https://icksw.tistory.com/270</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;br /&gt;&lt;br /&gt;오늘은 WWDC 2020의 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10045/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Advances in diffable data sources&lt;/a&gt;라는 영상을 보고 정리한 글을 써보려고 합니다.&lt;br /&gt;&lt;br /&gt;개발을 하는데 하나의 뷰에서 섹션별로 데이터도 쉽게 처리할 수 있는 Diffable Data Source라는 엄청난 게 있다길래 공부해서 저도 써보려고 이번 영상을 보게 되었습니다.  &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Advances in diffable data sources&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상에서는 iOS 14에서 추가된 diffable data source에 대해 다룬다고 합니다. 엥 더 이전에 나온 관련 영상도 있었네요. 일단 지금 영상이 짧으니까 이거 보고 이전에 나온 영상도 봐야겠네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/220/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Advances in UI Data Sources&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;어쨌든 이번 영상에서는 &quot;Emoji Exploer&quot;라는 샘플 앱을 가지고 설명한다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RFMcD/btrnUOwsezf/gXXkd5gdov26Sm8Jl3gDdk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RFMcD/btrnUOwsezf/gXXkd5gdov26Sm8Jl3gDdk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RFMcD/btrnUOwsezf/gXXkd5gdov26Sm8Jl3gDdk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/RFMcD/btrnUOwsezf/gXXkd5gdov26Sm8Jl3gDdk/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;602&quot; height=&quot;338&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 앱은 위 움짤을 보면 위에서 부터 3가지 부분으로 나뉩니다.&lt;br /&gt;&lt;br /&gt;첫 번째 섹션은 좌우로 스크롤되는 이모티콘 그리드 영역이 있고 두 전째 섹션은 iOS 14에서 추가된 접을 수 있는 스타일의 outline style UI라고 합니다. 그리고 마지막 섹션에는 위에서 보이는 UI는 컬렉션 뷰인데 그 안에 있는 테이블 뷰같이 보이는 영역입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTx1Rm/btrnVasvWis/KxVYQWGVNwP98TavFchzGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTx1Rm/btrnVasvWis/KxVYQWGVNwP98TavFchzGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTx1Rm/btrnVasvWis/KxVYQWGVNwP98TavFchzGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTx1Rm%2FbtrnVasvWis%2FKxVYQWGVNwP98TavFchzGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;306&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 iOS 13에서 도입된 Diffable Data Source를 사용하면 새로운 Snapshot 데이터 타입을 추가해서 UI 상태 관리를 단순화할 수 있다고 합니다.&lt;br /&gt;&lt;br /&gt;여기서 Snapshot은 고유한 section 및 item identifier를 사용해서 전체 UI 상태를 캡슐화하는 것을 말하는데 이를 사용해서 CollectionView를 업데이트할 때 먼저 현재 UI 상태로 새로운 snapshot을 만들어서 Data Source에 적용한다고 합니다. 이렇게 사용하는 Diffable Data Source를 사용하면 개발자가 추가 코드를 작성하지 않아도 애니메이션이나 변화를 줄 수 있다고 합니다.&lt;br /&gt;&lt;br /&gt;이럴 때 사용하는 API들은 아까 글 초반에 언급한 WWDC 2019 영상에서 자세히 다룬다고 하네요..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/220/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Advances in UI Data Sources&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uPY7j/btrnVx8RSgT/kEbnsJ794yBJ2fKEXCxtS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uPY7j/btrnVx8RSgT/kEbnsJ794yBJ2fKEXCxtS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uPY7j/btrnVx8RSgT/kEbnsJ794yBJ2fKEXCxtS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuPY7j%2FbtrnVx8RSgT%2FkEbnsJ794yBJ2fKEXCxtS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;224&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 이번 영상에서는 iOS 14에서 추가된 Diffable Data Source의 새로운 기능인 Section Snapshot, Reordering Support에 대해서 알아본다고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Section Snapshots&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Section Snapshots를 알아봅시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcy5i9/btrnVx8RTAh/W5cZOVdB44b7OISHHDYkX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcy5i9/btrnVx8RTAh/W5cZOVdB44b7OISHHDYkX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcy5i9/btrnVx8RTAh/W5cZOVdB44b7OISHHDYkX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcy5i9%2FbtrnVx8RTAh%2FW5cZOVdB44b7OISHHDYkX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1572&quot; height=&quot;500&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 14부터 Section snapshots도 추가되었습니다. 이름에서도 알 수 있듯 뭔가 UICollectionView의 섹션에 대한 일을 할 거 같은데요, 역시나 UICollectionView의 단일 섹션에 대한 데이터를 캡슐화한다고 합니다. 이걸 만든 두 가지 이유가 있다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Data Source를 섹션 단위로 구성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;iOS 14에서 추가된 Outline style UI를 지원하기 위해 필요한 계층적 데이터의 모델링을 허용하기 위함 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;다시 샘플 앱으로 돌아와서 Section Snapshot을 사용해서 앱을 만드는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btRytK/btrnXZjfcLG/RSRcF5KBCn2o1f73wIhQok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btRytK/btrnXZjfcLG/RSRcF5KBCn2o1f73wIhQok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btRytK/btrnXZjfcLG/RSRcF5KBCn2o1f73wIhQok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtRytK%2FbtrnXZjfcLG%2FRSRcF5KBCn2o1f73wIhQok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;363&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위와 같이 수평 스크롤 섹션에서 Section snapshot을 사용해서 해당 섹션의 콘텐츠를 모델링합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GGVu7/btrnR5Zvd1L/vi5cWNj4kNaJZg7uzq7HVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GGVu7/btrnR5Zvd1L/vi5cWNj4kNaJZg7uzq7HVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GGVu7/btrnR5Zvd1L/vi5cWNj4kNaJZg7uzq7HVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGGVu7%2FbtrnR5Zvd1L%2Fvi5cWNj4kNaJZg7uzq7HVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;435&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 확장 가능하고 접을 수 있는 Outline-Style 섹션인 두 번째 섹션도 Section Snapshot을 사용해서 모델링합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbdqN/btrnW37bAPe/MeKWjG6lqOIdhqrJe4A0T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbdqN/btrnW37bAPe/MeKWjG6lqOIdhqrJe4A0T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbdqN/btrnW37bAPe/MeKWjG6lqOIdhqrJe4A0T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbdqN%2FbtrnW37bAPe%2FMeKWjG6lqOIdhqrJe4A0T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;389&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 섹션 역시 Section Snapshot으로 모델링합니다.&lt;br /&gt;&lt;br /&gt;결국 이렇게 되면 현재 앱의 Diffable Data Source는 섹션별로 하나의 Section Snapshot, 즉 총 3개의 Section Snapshot으로 구성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0QFLc/btrnV21LcMa/H9SMOYcVJCi7xW2OfGwk7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0QFLc/btrnV21LcMa/H9SMOYcVJCi7xW2OfGwk7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0QFLc/btrnV21LcMa/H9SMOYcVJCi7xW2OfGwk7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0QFLc%2FbtrnV21LcMa%2FH9SMOYcVJCi7xW2OfGwk7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1624&quot; height=&quot;906&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 API를 살펴보겠습니다. iOS 13에서 도입된 snapshot을 말할 땐 &quot;Snapshot&quot;으로 표기하고 지금 배우고 있는 iOS 14에서 도입된 snapshot을 말할 땐 &quot;Section Snapshot&quot;으로 언급해서 구분하겠습니다.&lt;br /&gt;&lt;br /&gt;새로운 Section Snapshot 타입을 살펴보면 제네릭을 사용하는 것을 볼 수 있습니다. 기존 Snapshot과 다르게 identifier가 없습니다. 이는 Section Snapshot이 실제로 어떤 섹션을 나타내는지 모른다는 걸 뜻한다고 하네요.&lt;br /&gt;&lt;br /&gt;append API를 사용하면 Section Snapshot에 콘텐츠를 추가할 수 있다고 합니다. append의 매개변수 중에서 items는 추가하려는 콘텐츠를 말하는데, 그럼 parent가 뜻하는 건 뭘까요? parent에 nil이 아닌 값이 제공되면 계층 데이터를 모델링하는데 필요한 상위 하위 관계를 생성할 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCSEdE/btrnUOJ1QAN/DatNyatZ7PKxzX5K3TX6fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCSEdE/btrnUOJ1QAN/DatNyatZ7PKxzX5K3TX6fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCSEdE/btrnUOJ1QAN/DatNyatZ7PKxzX5K3TX6fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCSEdE%2FbtrnUOJ1QAN%2FDatNyatZ7PKxzX5K3TX6fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1654&quot; height=&quot;654&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 저렇게 생긴 Section Snapshot을 사용하기 위해 UICollectionViewDiffableDataSource에 두 개의 API도 추가되었습니다. &lt;br /&gt;&lt;br /&gt;첫 번째 API는 Section Snapshot과 Section Identifier를 사용하는 &quot;apply&quot;입니다. &lt;br /&gt;두 번째 API인 &quot;snapshot&quot;을 사용하면 특정 섹션의 내용을 나타내는 Section Snapshot을 찾을 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmWKs3/btrnVsGm9Cp/NxkdDv2xKHBnbtelm9qze0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmWKs3/btrnVsGm9Cp/NxkdDv2xKHBnbtelm9qze0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmWKs3/btrnVsGm9Cp/NxkdDv2xKHBnbtelm9qze0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmWKs3%2FbtrnVsGm9Cp%2FNxkdDv2xKHBnbtelm9qze0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1516&quot; height=&quot;760&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Snapshot과 Section Snapshot을 함께 사용해서 CollectionView를 구성하는 방법을 자세히 살펴보겠습니다.&lt;br /&gt;&lt;br /&gt;일단 위 코드와 같이 Diffable Data Source에 apply 메서드를 사용해서 원하는 순서로 섹션을 추가합니다. 위 코드에서는 recent, top, suggested라는 순서로 섹션이 구성된 것을 볼 수 있어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH8Wy5/btrnUJu6xAn/5ZUiDf5dP9Pn0i1DhJQubk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH8Wy5/btrnUJu6xAn/5ZUiDf5dP9Pn0i1DhJQubk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH8Wy5/btrnUJu6xAn/5ZUiDf5dP9Pn0i1DhJQubk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH8Wy5%2FbtrnUJu6xAn%2F5ZUiDf5dP9Pn0i1DhJQubk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1556&quot; height=&quot;772&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 위와 같이 섹션마다 Section Snapshot을 만들어서 각 섹션에 apply 메서드로 적용해줍니다. 그럼 이제 각 섹션은 각각의 Section Snapshot으로 item을 채우게 될 거예요.&lt;br /&gt;&lt;br /&gt;그럼 다음으로 두 번째 섹션이었던 Outline-Style UI를 구성하는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mSAMW/btrnWSkmBWy/dNoina7cW6D182EsZFgzcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mSAMW/btrnWSkmBWy/dNoina7cW6D182EsZFgzcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mSAMW/btrnWSkmBWy/dNoina7cW6D182EsZFgzcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmSAMW%2FbtrnWSkmBWy%2FdNoina7cW6D182EsZFgzcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1458&quot; height=&quot;860&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 섹션에 Root 아이템을 추가합니다. 이때 append API를 사용하는 것을 볼 수 있어요. 아까 append의 parent 매개변수에 값이 있으면 계층 구조를 만들 수 있다고 했잖아요? 그래서 위와 같이 Food를 parent로 제공하면 오른쪽에 보이는 UI와 같이 계층 구조를 만들 수 있습니다. 정말 간단하네요!&lt;br /&gt;&lt;br /&gt;그럼 이제 Section Snapshot을 사용해서 계층 구조를 만들 수 있다는 것을 알았는데, 가끔은 계층 구조에서 일부만 필요할 때도 있겠죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TWX20/btrnVwIT2hg/UGzMLL2hi8oQi4VkpufNI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TWX20/btrnVwIT2hg/UGzMLL2hi8oQi4VkpufNI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TWX20/btrnVwIT2hg/UGzMLL2hi8oQi4VkpufNI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTWX20%2FbtrnVwIT2hg%2FUGzMLL2hi8oQi4VkpufNI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1694&quot; height=&quot;534&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때 위 코드와 같이 특정 계층의 모든 하위 목록을 가지고 올 수도 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9ByQR/btrnQ5Z1cZ9/ltuBUbZLa9BeOqjtVtcww0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9ByQR/btrnQ5Z1cZ9/ltuBUbZLa9BeOqjtVtcww0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9ByQR/btrnQ5Z1cZ9/ltuBUbZLa9BeOqjtVtcww0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9ByQR%2FbtrnQ5Z1cZ9%2FltuBUbZLa9BeOqjtVtcww0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1642&quot; height=&quot;808&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음은 Expanding 상태에 대해 살펴보겠습니다. Expanding 상태도 Section Snapshot 상태의 일부로 관리되는데, 간단하게 말해서 하위 데이터가 보일지 말지를 결정할 수 있는 기능입니다.&lt;br /&gt;&lt;br /&gt;표시할 Snapshot을 만들 때 해당 항목의 상위 Expanding 상태를 설정해서 하위 콘텐츠가 표시될지 말지를 결정할 수 있습니다. Snapshot을 검색해서 하위 콘텐츠가 보이는지 안 보이는 지도 확인할 수 있다고 하네요. 이렇게 Section Snapshot의 Expanding 상태를 변경하면 Diffable Data Source의 apply를 호출할 때까지는 적용되지 않는다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNsdlC/btrnVxgJitT/noGXCmOlbL97KMntQ3glh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNsdlC/btrnVxgJitT/noGXCmOlbL97KMntQ3glh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNsdlC/btrnVxgJitT/noGXCmOlbL97KMntQ3glh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNsdlC%2FbtrnVxgJitT%2FnoGXCmOlbL97KMntQ3glh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;318&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Expansion 상태를 정리해보면, 아까 알아본 대로 Section Snapshot이 이를 관리하며 표시할 Snapshot을 만들 때 상위 계층의 Expansion 상태를 설정해서 하위 계층이 처음에 표시될지 말지 결정할 수 있다고 했습니다. 또한 Snapshot을 쿼리 해서 Expansion 상태를 확인할 수도 있고 Diffable Data Source의 apply를 호출하기 전까지는 변경사항이 적용되지 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgFX5F/btrnVazjGl7/I4P7ikobPPgBL9MQ3dZeK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgFX5F/btrnVazjGl7/I4P7ikobPPgBL9MQ3dZeK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgFX5F/btrnVazjGl7/I4P7ikobPPgBL9MQ3dZeK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgFX5F%2FbtrnVazjGl7%2FI4P7ikobPPgBL9MQ3dZeK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;752&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 방금 알아본 기능을 위한 API를 살펴보겠습니다. 위 코드를 보면 Diffable Data Source에 &quot;SectionSnapshotHandlers&quot;라는 새로운 타입이 있는 것을 알 수 있습니다. 새로운 타입은 Item에 대한 제네릭이고 5개의 optional 클로저를 가지고 있는 구조체네요.&lt;br /&gt;&lt;br /&gt;방금 알아본 Expansion 상태에 대한 요구사항을 처리하기 위한 다양한 handler가 구현되어있습니다. 여기서 snapshotForExpandingParent를 사용하면 크기가 큰 콘텐츠에 대한 lazy loading도 가능하다고 합니다. 이는 어떤 콘텐츠를 가지고 올 때 자원이 많이 든다면 초기 Section Snapshot에 로드된 콘텐츠의 양을 최소화하는데 유용하게 사용할 수 있다고 합니다. 즉 계층이 깊게 있다면 모든 자식 콘텐츠를 가지고 오지 않고 필요한 만큼만 로드해서 사용하겠다는 의미로 보입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reordering Support&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Diffable Data Source의 새로운 기능 중 하나는 CollectionView의 데이터를 고유한 item identifier로 모델링하는 기능입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5w5GI/btrnR38OrYP/AthXNBMiAJkb6iPRtEJ5u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5w5GI/btrnR38OrYP/AthXNBMiAJkb6iPRtEJ5u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5w5GI/btrnR38OrYP/AthXNBMiAJkb6iPRtEJ5u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5w5GI%2FbtrnR38OrYP%2FAthXNBMiAJkb6iPRtEJ5u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;444&quot; height=&quot;295&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 고유한 item identifier를 사용하면 프레임워크가 사용자 상호작용을 기반으로 앱을 대신해서 reorder 작업을 커밋할 수 있습니다. 하지만 이것만으로는 충분하지 않고, 앱에 사용자가 reorder 작업을 발생했음을 알려야지 최종 source of truth인 백업 저장소에 새로운 순서로 된 UI를 유지할 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IAh44/btrnQ7cxCEN/D6EX0A0AMhugS8JnykR1TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IAh44/btrnQ7cxCEN/D6EX0A0AMhugS8JnykR1TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IAh44/btrnQ7cxCEN/D6EX0A0AMhugS8JnykR1TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIAh44%2FbtrnQ7cxCEN%2FD6EX0A0AMhugS8JnykR1TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;832&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 reordering을 지원하기 위해 Diffable Data Source에는 ReorderingHandlers라는 새로운 타입이 정의되었습니다. 얘도 3개의 optional 클로저를 갖는 구조체로, 이를 활용해서 reordering을 활성화하려면 먼저 canReorderItem 클로저를 제공해야 한다고 합니다.&lt;br /&gt;&lt;br /&gt;canReorderItem은 사용자가 reorder 작업을 시작하려고 할 때 호출되며, true를 반환하면 작업을 시작할 수 있습니다. 작업이 완료되면 didReorder가 호출되어 앱이 새로운 상태를 앱의 source of truth에 커밋할 수 있도록 해줍니다. 즉 reordering 기능을 사용하기 위해선 canReorderItem, didReorder 클로저를 모두 제공해야 한다는 것을 의미합니다.&lt;br /&gt;&lt;br /&gt;여기서 didReorder 클로저는 새로운 타입인 NSDiffableDataSourceTransaction을 전달하는데 얘가 뭔지도 살펴보겠습니다.&lt;br /&gt;&lt;br /&gt;트랜잭션은 Diffable Data Source에 대해 수행되는 업데이트를 추론하는데 필요한 모든 정보를 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVxwo5/btrnWTcwM2j/JwprHkxKKlULGBpwrV9phk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVxwo5/btrnWTcwM2j/JwprHkxKKlULGBpwrV9phk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVxwo5/btrnWTcwM2j/JwprHkxKKlULGBpwrV9phk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVxwo5%2FbtrnWTcwM2j%2FJwprHkxKKlULGBpwrV9phk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1546&quot; height=&quot;312&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 NSDiffableDataSourceTransction을 살펴보면, 네 개의 기본 정보를 제공하네요. 먼저 업데이트되기 전의 Data Source 상태인 initialSnapshot이 있네요. 그리고 업데이트 후 상태인 finalSnapshot이 있습니다. 이 Snapshot의 item identifier를 사용해서 앱의 소스에 커밋해야 하는 새로운 순서를 결정할 수 있다고 합니다.&lt;br /&gt;&lt;br /&gt;그리고 swift 표준 라이브러리에서 제공하는 CollectionDifference 타입인 differance가 있습니다. 앱에서 source of truth에 대한 Array와 같은 데이터 타입이 있는 경우 CollectionDifference를 해당 데이터 타입에 직접 적용할 수 있다고 하는데.. 뭔 말인지 이해가 잘 안 되네요. ㅠ&lt;br /&gt;&lt;br /&gt;마지막으로 현재 ordering 작업과 관련된 모든 섹션에 대한 섹션별 세부 정보를 제공하는 sectionTransactions가 있습니다. 그럼 다음으로는 NSDiffableDataSourceSectionTransaction도 알아봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehau1j/btrnTD24LtX/a2C57nbY9KH8KrAMcW7KmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehau1j/btrnTD24LtX/a2C57nbY9KH8KrAMcW7KmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehau1j/btrnTD24LtX/a2C57nbY9KH8KrAMcW7KmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fehau1j%2FbtrnTD24LtX%2Fa2C57nbY9KH8KrAMcW7KmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1552&quot; height=&quot;326&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NSDiffableDataSourceSectionTransaction도 비슷한데요, 얘네는 하나의 섹션에 대한 트랜잭션만 제공됩니다. 그래서 이를 구분하기 위한 sectionIdentifier가 있네요. 그 외엔 똑같습니다. 물론 하나의 섹션에 대한 트랜잭션이니 sectionTransactions와 같은 정보는 필요 없어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SaxVy/btrnU6jf5n6/0REsphsZ9Rr3NB41CmSSmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SaxVy/btrnU6jf5n6/0REsphsZ9Rr3NB41CmSSmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SaxVy/btrnU6jf5n6/0REsphsZ9Rr3NB41CmSSmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSaxVy%2FbtrnU6jf5n6%2F0REsphsZ9Rr3NB41CmSSmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1660&quot; height=&quot;796&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 살펴보면, 위 코드의 backingStore는 하나의 섹션이 있는 CollectionView에 대한 정보를 제공하는 [Item]입니다. 트랜잭션과 함께 제공된 CollectionDifference를 사용해서 새로운 백업 저장소를 만들고 source of truth를 직접 업데이트한다고 합니다.&lt;br /&gt;&lt;br /&gt;이렇게 영상은 끝이 납니다.&lt;br /&gt;일단 이번 영상은 iOS 14에서 Diffable Data Source에 추가된 기능인 Section Snapshot과 Reordering Taansactions를 알아보는 영상이었네요.. Diffable Data Source의 기본 기능을 공부하고 싶었는데, 추가 기능부터 알게 된 느낌입니다.  &lt;br /&gt;&lt;br /&gt;데이터를 계층 구조로 처리하는 기능이나 섹션별로 Snapshot을 다루는 기능, Reordering 기능들이 모두 유용해 보이긴 하는데, 직접 사용을 해봐야 확실히 알 수 있겠네요.&lt;br /&gt;&lt;br /&gt;공부할게 정말 많네요  &lt;br /&gt;&lt;br /&gt;감사합니다.&lt;/p&gt;</description>
      <category>Apple/WWDC 2020</category>
      <category>2020</category>
      <category>CollectionView</category>
      <category>Datasource</category>
      <category>Diffable</category>
      <category>IOS</category>
      <category>SECTION</category>
      <category>snapshot</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/270</guid>
      <comments>https://icksw.tistory.com/270#entry270comment</comments>
      <pubDate>Wed, 15 Dec 2021 01:00:05 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2021] Protect mutable state with Swift actors</title>
      <link>https://icksw.tistory.com/269</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 WWDC 2021의 &quot;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10133/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Protect mutable state with Swift actors&lt;/a&gt;&quot;이라는 영상을 보고 정리한 글을 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 Swift 프로그래밍을 할 때 가변 상태를 보호하기 위해 사용합니다. Class, Struct와 비슷하게 사용하는데요, 이를 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protect&amp;nbsp;mutable&amp;nbsp;state&amp;nbsp;with&amp;nbsp;Swift&amp;nbsp;actors&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0csR/btrmyDpPkah/0erxsQKVWEXY12jXmbemT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0csR/btrmyDpPkah/0erxsQKVWEXY12jXmbemT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0csR/btrmyDpPkah/0erxsQKVWEXY12jXmbemT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0csR%2FbtrmyDpPkah%2F0erxsQKVWEXY12jXmbemT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1604&quot; height=&quot;872&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 프로그램을 만들 때 어려운 문제중 하나는 data races입니다. Data races는 두 개의 다른 스레드가 동일한 데이터에 접근하는데, 둘 중 하나가 쓰기 작업일 때 발생합니다. 이는 디버깅하기도 어려워서 문제가 발생해도 찾기가 힘들죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 간단하게 value를 1씩 증가시키는 기능을 갖고 있는 Counter 클래스를 만들었습니다. 그리고 2개의 concurrent task를 실행해서 값을 증가시키는 작업을 구현했다고 하면, 이는 잘못된 구현입니다. 실행에 따라 1을 얻고 2를 얻을 수도 있고 2를 얻고 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;Data races는 디버깅하기도 어려워서 방금같이 간단한 문제도 찾기 어려울 수 있습니다. 또한 OS의 스케줄러는 프로그램을 실행할 때 마다 concurrent task를 다르게 실행할 수 있어서 발생한 문제 자체도 항상 바뀔 수 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Data races는 shared mutable state(변경 가능한 공유 상태)로 인해 발생합니다. 즉 데이터가 변경되지 않거나 다른 스레드와 공유되지 않는 경우엔 발생하지 않습니다. 따라서 Data races를 피하는 한 가지 방법은 value semantic을 사용해서 shared mutable state를 제거하는 것입니다. 예를 들어 Swift에서 let을 사용해서 변수를 만들면 변경이 불가능해져서 data races로부터 안전합니다.&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;1576&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTL6cV/btrmEidTySX/h7Ub7mpy534aKOr4QV9tB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTL6cV/btrmEidTySX/h7Ub7mpy534aKOr4QV9tB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTL6cV/btrmEidTySX/h7Ub7mpy534aKOr4QV9tB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTL6cV%2FbtrmEidTySX%2Fh7Ub7mpy534aKOr4QV9tB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1576&quot; height=&quot;868&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보며 value semantics를 이해해봅시다. 위 코드에서는 값이 있는 배열 array1을 만들고 array2에는 만들어진 array1을 할당합니다. 그런 뒤 각 복사본에 다른 값을 추가한 뒤 print 해보면, 서로 다른 값이 들어있는 것을 알 수 있습니다. 즉 Swift 표준 라이브러리의 대부분의 타입은 위와 같이 value semantics을 사용하므로 data races를 피할 수 있도록 설계되어있습니다.&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;1586&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLH3Lh/btrmEjqn4Q1/K1j0TDkCuWFUGvuGBoKYRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLH3Lh/btrmEjqn4Q1/K1j0TDkCuWFUGvuGBoKYRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLH3Lh/btrmEjqn4Q1/K1j0TDkCuWFUGvuGBoKYRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLH3Lh%2FbtrmEjqn4Q1%2FK1j0TDkCuWFUGvuGBoKYRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1586&quot; height=&quot;892&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 아까 data races가 발생했던 예제에서 data races를 제거해봅시다. 아까와 다르게 Counter를 클래스가 아닌 구조체로 바꾸면 됩니다. 구조체 프로퍼티를 수정하기 위해서는 mutating 키워드도 붙여줘야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLJYBh/btrmDXHQ96v/IXowu4HgZgmlAtnYxgl4yk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLJYBh/btrmDXHQ96v/IXowu4HgZgmlAtnYxgl4yk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLJYBh/btrmDXHQ96v/IXowu4HgZgmlAtnYxgl4yk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bLJYBh/btrmDXHQ96v/IXowu4HgZgmlAtnYxgl4yk/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;602&quot; height=&quot;338&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 아까와 같이 counter를 객체를 만들어서 수정하려고 하면 counter가 let으로 선언된 변수라서 변경할 수 없다고 합니다. 따라서 var로 수정한 뒤 실행하면 될 것 같지만.. 이렇게 하면 counter가 두 개의 concurrent task에서 참조되므로 data races는 해결되지 않고 다시 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 컴파일러에서 이러한 코드를 문제 삼기 때문에 컴파일되지는 않습니다. 그럼 어떻게 해야 해결할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vtuKG/btrmzjrxzFA/rK1AA0UI1s0hpu0DwrlpfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vtuKG/btrmzjrxzFA/rK1AA0UI1s0hpu0DwrlpfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vtuKG/btrmzjrxzFA/rK1AA0UI1s0hpu0DwrlpfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvtuKG%2FbtrmzjrxzFA%2FrK1AA0UI1s0hpu0DwrlpfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;870&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 data races를 해결하기 위해서는 각각의 concurrent task 내부에 mutable 변수로 counter를 만들어 처리하면 됩니다. 물론 이건 원하는 결과가 아닙니다.&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;998&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWEXA8/btrmzLVrIyM/yAxSGUSHfSkrXUUshnT4u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWEXA8/btrmzLVrIyM/yAxSGUSHfSkrXUUshnT4u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWEXA8/btrmzLVrIyM/yAxSGUSHfSkrXUUshnT4u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWEXA8%2FbtrmzLVrIyM%2FyAxSGUSHfSkrXUUshnT4u1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;998&quot; height=&quot;608&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 결과를 얻기 위해 동시성 프로그램에서 shared mutable state가 있는 경우 이로 인해 data races를 발생하지 않도록 동기화가 필요합니다. Atomics(원자성), Locks, Serial dispatch queue와 같은 것들이 이런 문제를 해결할 수 있습니다. 이런 것을 사용하면 장점도 있긴 하지만 모두 주의해서 사용하지 않으면 data races는 해결되지 않을 거예요.&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;Actors&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lApNS/btrmzK3uKRN/8VOqP04Nq0tK2lUJQKPxyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lApNS/btrmzK3uKRN/8VOqP04Nq0tK2lUJQKPxyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lApNS/btrmzK3uKRN/8VOqP04Nq0tK2lUJQKPxyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlApNS%2FbtrmzK3uKRN%2F8VOqP04Nq0tK2lUJQKPxyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;538&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등장한 것이 Actor입니다. Actor는 shared mutable state를 위한 동기화 메커니즘으로, Actor에는 자체적인 상태가 있으며 해당 상태는 프로그램의 나머지 부분과 분리되어있습니다. 따라서 해당 상태에 접근하는 유일한 방법은 Actor를 통해서만 가능하며, Actor를 통과할 때마다 Actor의 동기화 메커니즘은 다른 코드가 Actor의 상태에 동시에 접근하지 않도록 해줍니다. 이는 Lock, Serial Dispatch Queue를 사용해서 직접 처리하는 것과 동일한 상호 배제효과를 내지만 Actor는 Swift에서 기본적으로 제공해주므로 편리합니다. 또한 동기화를 수행해주지 않으면 컴파일러가 오류를 발생시킵니다.&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;1014&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yQTHf/btrmFn0nacX/Pm4Ea5s5lZkgqeQGbpNVcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yQTHf/btrmFn0nacX/Pm4Ea5s5lZkgqeQGbpNVcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yQTHf/btrmFn0nacX/Pm4Ea5s5lZkgqeQGbpNVcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyQTHf%2FbtrmFn0nacX%2FPm4Ea5s5lZkgqeQGbpNVcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;470&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 Swift의 새로운 타입입니다. 이는 기존에 존재하던 Swift의 타입들과 동일한 기능을 제공하며 프로퍼티, 메서드, 생성자, subscript 등을 가질 수 있습니다. 또한 프로토콜을 채택하고 extension을 사용할 수도 있습니다. Actor의 목적은 Shared mutable state를 표현하는 것이므로 Actor는 Class와 동일하게 참조 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor 타입의 특징은 인스턴스 데이터를 프로그램의 나머지 부분과 분리하고 해당 데이터에 대한 동기화된 접근을 보장한다는 것입니다. 이 핵심 아이디어가 Actor에서 가장 중요하다고 할 수 있다고 합니다.&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;1578&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0XdCX/btrmEdjDwTL/N4Jeode0zGKmOkAjG2CpFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0XdCX/btrmEdjDwTL/N4Jeode0zGKmOkAjG2CpFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0XdCX/btrmEdjDwTL/N4Jeode0zGKmOkAjG2CpFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0XdCX%2FbtrmEdjDwTL%2FN4Jeode0zGKmOkAjG2CpFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1578&quot; height=&quot;380&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 간단하게 사용해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 구현했던 Counter를 Actor 타입으로 수정해봅니다. Counter에 대한 value라는 프로퍼티와 이를 1 증가시키는 increment() 메서드는 그대로 존재합니다. 아까와 차이점은 Actor는 value에 동시에 접근되지 않도록 만들어준다는 것 입니다. 즉 만약 increment() 메서드가 호출되면 완료될 때까지 실행됩니다. 이러한 보장은 Data races가 발생하지 않게 해 줍니다.&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;1572&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAQJ7/btrmFoZhmkL/oKdNiiGkG1k1M5LwkkfEO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAQJ7/btrmFoZhmkL/oKdNiiGkG1k1M5LwkkfEO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAQJ7/btrmFoZhmkL/oKdNiiGkG1k1M5LwkkfEO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAQJ7%2FbtrmFoZhmkL%2FoKdNiiGkG1k1M5LwkkfEO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1572&quot; height=&quot;864&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와 같이 2개의 concurrent Task를 만들어서 각각의 Task에서 동일한 인스턴스에 increment()를 호출합니다. Actor의 내부 동기화 메커니즘은 하나의 increment()가 완료된 후에 다음 increment()가 실행되도록 해줍니다. 따라서 실제로 실행해보면 하나가 먼저 처리되고 그다음이 처리되는 것을 볼 수 있을 거예요.&lt;/p&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 Task는 어떻게 해서 먼저 실행 중인 increment()를 기다린 뒤에 실행하는 걸까요? Swift에는 이를 위한 메커니즘이 있다고 합니다.&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;1574&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z8NFE/btrmGhMm7FJ/XiVKUVhthluTq3kx0OkBbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z8NFE/btrmGhMm7FJ/XiVKUVhthluTq3kx0OkBbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z8NFE/btrmGhMm7FJ/XiVKUVhthluTq3kx0OkBbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz8NFE%2FbtrmGhMm7FJ%2FXiVKUVhthluTq3kx0OkBbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;842&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 Actor와 상호작용할 때마다 비동기로 처리됩니다. 만약 Actor가 사용 중이라면 사용 중인 CPU가 Actor에 접근 중인 코드를 일시 정지하고 다른 작업을 수행합니다. 이후에 Actor의 사용이 끝나면 정지해둔 코드를 다시 실행해서 Actor에 접근합니다. 위 코드에서 await 키워드는 Actor에 대한 비동기 호출이 일시 정지될 수도 있음을 나타냅니다.&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;1596&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1D2oK/btrmyjrUBsp/5y3tbERLKrIQd4tbvaSwF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1D2oK/btrmyjrUBsp/5y3tbERLKrIQd4tbvaSwF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1D2oK/btrmyjrUBsp/5y3tbERLKrIQd4tbvaSwF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1D2oK%2FbtrmyjrUBsp%2F5y3tbERLKrIQd4tbvaSwF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1596&quot; height=&quot;884&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 많은 작업을 수행하는 코드로 Actor를 좀 더 이해해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 value를 다시 0으로 설정한 뒤 적절한 횟수만큼 increment()를 호출해서 value를 새로운 값으로 가지고 오는 resetSlowly() 메서드입니다. 위 메서드는 Counter Actor 타입의 Extension에 정의되어있어 Actor 내부에 있습니다. 즉 value값을 0으로 재설정하기 위해 Actor의 상태에 직접 접근할 수 있습니다. 또한 increment()과 같이 Actor의 다른 메서드를 동기적으로 호출할 수도 있으며, 이미 Actor에서 실행 중이란 것을 알고 있어서 대기도 필요하기 않습니다. Actor의 동기 코드는 중단 없이 완료될 때까지 실행되므로 동시성 프로그래밍에서 주의할 점을 고려할 필요가 없죠.&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;Actor reentrancy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기 코드는 중단 없이 실행되지만 아까 Actor는 시스템의 다른 비동기 코드와 상호작용한다고 했었는데요, 비동기 코드와 Actor에 대해 알아보기 위해 다음 예를 살펴봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NhiQ8/btrmCDpCkRm/QFBWWKTcdKn7acSXw1KJX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NhiQ8/btrmCDpCkRm/QFBWWKTcdKn7acSXw1KJX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NhiQ8/btrmCDpCkRm/QFBWWKTcdKn7acSXw1KJX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNhiQ8%2FbtrmCDpCkRm%2FQFBWWKTcdKn7acSXw1KJX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1074&quot; height=&quot;642&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 이미지 다운로드 Actor입니다. 다른 서비스에서 이미지를 다운로드하는 역할을 합니다. 또한 다운로드한 이미지를 캐시에 저장해서 동일한 이미지는 여러 번 다운로드하지 않도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직은 간단합니다. 캐시를 확인하고 캐시에 이미지가 없다면 다운로드한 뒤 캐시에 저장하고 나서 반환합니다. 이는 Actor에서 실행되는 코드이므로 data races로부터 안전합니다. 따라서 원하는 수의 이미지를 동시에 다운로드할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor의 동기화 메커니즘은 한 번에 하나의 작업만 캐시 인스턴스에 접근하도록 보장하므로 캐시가 손상될 수 있는 경우는 없다고 생각할 테지만, 여기서 await 키워드가 문제를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await가 발생할 때마다 해당 시점에서 함수는 일시 정지될 수 있음을 의미합니다. 프로그램의 다른 코드가 실행될 수 있도록 CPU를 포기해서 전체 프로그램의 상태에 영향을 줍니다. 함수가 다시 실행되는 시점에 전체 프로그램 상태가 변경되며, await 이후에 유지되지 않을 수 있는 상태를 정의하지 않았는지 확인하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yjr76/btrmzjyqEfp/oeZjjTpkkaNqTy6mRh1KwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yjr76/btrmzjyqEfp/oeZjjTpkkaNqTy6mRh1KwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yjr76/btrmzjyqEfp/oeZjjTpkkaNqTy6mRh1KwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyjr76%2FbtrmzjyqEfp%2FoeZjjTpkkaNqTy6mRh1KwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1518&quot; height=&quot;732&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 동일한 이미지를 동시에 가지고 오려고하는 두 개의 concurrent task가 있다고 해보겠습니다. 첫 번째는 캐시에 이미지가 없음을 확인하고 서버에서 웃고있는 고양이 이미지 다운로드를 시작한 뒤 다운로드할 동안 다른 프로그램을 실행하기 위해 일시 정지됩니다. 이렇게 첫 번째 작업이 이미지를 다운로드하는 동안 동일한 URL의 서버에 새로운 이미지가 올라올 수 있습니다.&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;1544&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X1F8N/btrmEEuwxHb/CK6vYifKPiFYIMiuyk3nK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X1F8N/btrmEEuwxHb/CK6vYifKPiFYIMiuyk3nK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X1F8N/btrmEEuwxHb/CK6vYifKPiFYIMiuyk3nK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX1F8N%2FbtrmEEuwxHb%2FCK6vYifKPiFYIMiuyk3nK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1544&quot; height=&quot;710&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 작업이 동일한 URL로 이미지를 가지고오려고 하는데, 첫 번째 다운로드가 아직 완료되지 않아 캐시에 존재하지 않고 동일한 URL이미지 다운로드를 시작합니다. 물론 이 경우에도 다른 프로그램을 실행하기 위해 일시정지됩니다. 근데 이 때는 이미지가 변경되어 울고 있는 고양이 이미지를 다운로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠시 후 첫 번째 다운로드가 완료되고 해당 작업이 Actor에서 실행을 다시 시작하면, 캐시에 이미지를 저장하고 웃고 있는 고양이 이미지를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 잠시 후 두 번째 다운로드가 완료되고 캐시에 울고 있는 고양이 이미지로 덮어쓴 뒤 반환합니다. 즉 이렇게 하면 동일한 URL로 이미지를 다운로드하였지만 다른 이미지를 얻게 됩니다. 이미지를 캐시 하면 동일한 URL에 대해 동일한 이미지를 가지고 올 줄 알았는데, 캐시 이미지가 변경된 것을 알 수 있습니다. Actor가 low-level의 data races는 없지만 await로 인해 버그가 발생한 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정할 것은 await후에 잘 수행되는지 확인하는 것입니다. 이를 위해 일시정지 후 다시 실행할 때 캐시에 이미 값이 있으면 원래 버전을 유지하고 새로운 버전을 버리도록 하던지, 동일한 URL에 대해서는 중복 다운로드를 아예 못하게 해 버리면 될 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6t3AS/btrmCByxzId/VbE8SbKpeV9kecGfONgmR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6t3AS/btrmCByxzId/VbE8SbKpeV9kecGfONgmR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6t3AS/btrmCByxzId/VbE8SbKpeV9kecGfONgmR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6t3AS%2FbtrmCByxzId%2FVbE8SbKpeV9kecGfONgmR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;514&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor reentrancy는 deadlock을 방지하고 forward progress를 보장하지만 await가 존재한다면 좀 더 주의해서 사용해야 합니다. 이를 잘 설계하기 위해서는 동기 코드 내에서 Actor의 상태 변경을 수행하면 됩니다. 가장 좋은 것은 동기 함수 내에서 수행해서 모든 상태 변경이 캡슐화되도록 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변경은 일시적으로 Actor를 일관성 없는 상태로 만드는 것을 포함하므로 await 전에 일관성을 복원해야만 합니다. await는 정지할 수 있는 지점이므로 코드가 다시 시작되기 전에도 프로그램은 계속 실행되고 있습니다. 따라서 다시 시작되면 global state, clocks, timer와 같은 것들을 확인해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Actor isolation에 대해 알아보겠습니다.&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;Actor isolation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor isolation은 Actor 타입의 동작의 기본입니다. 아까 Actor 외부와 비동기 상호작용을 통해 Swift가 Actor isolation을 보장하는 방법에 대해 알아봤었는데요, 이번 영상에서는 Actor isolation이 프로토콜 채택, 클로저, 클래스를 비롯한 다른 언어 기능과 상호작용하는 방식에 대해 알아본다고 합니다.&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;1596&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf0wOJ/btrmxL2XTpy/upX1aGXXUWhnFlsZSu7hz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf0wOJ/btrmxL2XTpy/upX1aGXXUWhnFlsZSu7hz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf0wOJ/btrmxL2XTpy/upX1aGXXUWhnFlsZSu7hz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf0wOJ%2FbtrmxL2XTpy%2FupX1aGXXUWhnFlsZSu7hz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1596&quot; height=&quot;874&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 타입과 마찬가지로 Actor는 프로토콜을 사용할 수 있습니다. 예를 들어 위와 같이 LibraryAccount Actor가 Equtable 프로토콜을 따르게 합니다. idNumber를 통해 같은 값인지 비교하게 되는데, 해당 메서드는 static이므로 인스턴스가 없어 Actor와 분리되지 않습니다. 대신 외부에서 두 개의 Actor를 매개변수를 받아 처리합니다. 위 구현은 Actor의 immutable 상태에만 접근되기 때문에 아무런 문제가 없습니다.&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;1576&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAkLSV/btrmzjyrKwR/Wrwq5hXsRc8xvgmoWnMLI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAkLSV/btrmzjyrKwR/Wrwq5hXsRc8xvgmoWnMLI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAkLSV/btrmzjyrKwR/Wrwq5hXsRc8xvgmoWnMLI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAkLSV%2FbtrmzjyrKwR%2FWrwq5hXsRc8xvgmoWnMLI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1576&quot; height=&quot;858&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 LibraryAccount가 Hashable 프로토콜을 따르도록 해봅시다. 그렇게 하려면 위와 같이 hash(into:) 메서드를 구현해야 하며, Swift 컴파일러는 문제를 발견합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Hashable을 준수하게 되면 함수를 Actor 외부에서 호출할 수 있지만 hash(into:) 함수가 비동기가 아니므로 Actor isolation을 유지할 방법이 없다는 것을 의미합니다.&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;1592&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qbkbG/btrmEikOze7/nOQEIhiYTpIE240v1AWork/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qbkbG/btrmEikOze7/nOQEIhiYTpIE240v1AWork/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qbkbG/btrmEikOze7/nOQEIhiYTpIE240v1AWork/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqbkbG%2FbtrmEikOze7%2FnOQEIhiYTpIE240v1AWork%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1592&quot; height=&quot;862&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 수정하기 위해서 메서드를 nonisolated로 만들면 됩니다. nonisolated는 이 메서드가 Actor 외부에 있는 것으로 처리된다는 것을 의미합니다. nonisolated 메서드는 Actor 외부에 있는 것처럼 처리되므로 Actor의 mutable state를 참조할 수 없습니다. 물론 위 코드에서 idNumber는 변경할 수 없으므로 문제는 없습니다. 하지만 bookOnLoan 프로퍼티와 같은 array로 hash를 시도하면 외부에서 mutable state에 접근하면 data races를 허용하므로 오류가 발생합니다.&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;1594&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p47Zt/btrmEdjF6Kc/94BRERoki64Fw6SwxrNmHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p47Zt/btrmEdjF6Kc/94BRERoki64Fw6SwxrNmHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p47Zt/btrmEdjF6Kc/94BRERoki64Fw6SwxrNmHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp47Zt%2FbtrmEdjF6Kc%2F94BRERoki64Fw6SwxrNmHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1594&quot; height=&quot;880&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 클로저에 대해 알아봅시다. 클로저는 하나의 함수에서 정의된 작은 함수로 다른 함수로 전달되어 나중에 호출될 수도 있습니다. 함수와 마찬가지로 클로저는 Actor로부터 isolated 될 수도 nonisolated 될 수도 있습니다. 위 코드는 대출한 책들 중 일부를 읽고 총 읽은 페이지 수를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reduce 호출에는 read()를 수행하는 클로저가 포함되며 readSome() 호출에는 await는 없습니다. Actor isolated 함수인 read()에서 만들어진 이 클로저 자체가 Actor isolated이기 때문입니다. 따라서 reduce 작업이 동기적으로 실행되고 동시 접근을 발생할 수 있는 다른 스레드로 이동할 수 없어서 위 코드가 안전하다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 조금 수정해서, 당장 읽는 게 아닌 나중에 읽는 작업으로 바꿔봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tfw1m/btrmziM344s/VadUNnof1kfkB0J3EaAB6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tfw1m/btrmziM344s/VadUNnof1kfkB0J3EaAB6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tfw1m/btrmziM344s/VadUNnof1kfkB0J3EaAB6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTfw1m%2FbtrmziM344s%2FVadUNnof1kfkB0J3EaAB6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1576&quot; height=&quot;878&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 detached task를 만듭니다. 이는 Actor가 수행하는 다른 작업과 클로저를 동시에 실행합니다. 다라서 클로저는 Actor에 있을 수 없고, 그로 인해 data races가 발생합니다. 따라서 위 클로저는 Actor와 nonisolated 합니다. read() 메서드를 호출하려면 await에 표시된 대로 비동기로 호출해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 코드가 Actor 내부에서 실행되는지 외부에서 실행되는지를 알 수 있는 Actor isolation에 대해 알아봤습니다. 그럼 이제는 actor isolation과 data에 대해 알아보겠습니다.&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;906&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOmHGf/btrmzisLteu/QYRjJcpdKiYQt3PHKdMjsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOmHGf/btrmzisLteu/QYRjJcpdKiYQt3PHKdMjsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOmHGf/btrmzisLteu/QYRjJcpdKiYQt3PHKdMjsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOmHGf%2FbtrmzisLteu%2FQYRjJcpdKiYQt3PHKdMjsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;906&quot; height=&quot;450&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 LibratryAccount에서 Book 타입이 뭔지는 몰랐는데요, 만약 Book이 구조체라고 해봅시다. 이는 LibraryAccount Actor의 인스턴스에 대한 모든 상태가 자체적으로 포함된다는 것이므로 좋은 결정입니다.&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;1190&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsYfRG/btrmAKbxpoU/t1RAkbjNT4DFKICFO5Vqd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsYfRG/btrmAKbxpoU/t1RAkbjNT4DFKICFO5Vqd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsYfRG/btrmAKbxpoU/t1RAkbjNT4DFKICFO5Vqd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsYfRG%2FbtrmAKbxpoU%2Ft1RAkbjNT4DFKICFO5Vqd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1190&quot; height=&quot;210&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체로 정의된 Book을 무작위로 선택했을 땐 Book의 복사본을 얻게 되며 해당 복사본에 대한 변경사항은 Actor에게 영향을 주지도 않고 그 반대도 마찬가지입니다.&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;1196&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eo843g/btrmDROJuuY/9EdXXWHmAcAKN8IyWVHQgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eo843g/btrmDROJuuY/9EdXXWHmAcAKN8IyWVHQgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eo843g/btrmDROJuuY/9EdXXWHmAcAKN8IyWVHQgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feo843g%2FbtrmDROJuuY%2F9EdXXWHmAcAKN8IyWVHQgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1196&quot; height=&quot;552&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Book이 클래스로 정의되면 달라집니다. LibraryAccount는 이제 Book 클래스의 인스턴스를 참조하게 됩니다. 아직 문제는 없어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 Book을 무작위로 선택하는 메서드를 호출하면 어떻게 될까요? 이제 Actor 외부에서 공유된 Actor의 mutable state에 대한 참조가 있습니다. 따라서 data races가 가능해집니다. 이제 책의 title을 수정하면 Actor 안에서 접근할 수 있는 state가 수정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visit() 메서드에는 Actor에 존재하지 않기 때문에 해당 수정이 data races를 발생시킬 수 있습니다. 즉 값 타입과 Actor는 함께 사용해도 안전하지만 참조 타입과 Actor는 함께 사용하면 문제를 발생시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 타입과 함께 사용해도 안전한 타입의 이름은 Sendable입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sendable Type&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHZGj1/btrmFoE21ef/DuqIGWJXUkah7KOP3kKkMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHZGj1/btrmFoE21ef/DuqIGWJXUkah7KOP3kKkMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHZGj1/btrmFoE21ef/DuqIGWJXUkah7KOP3kKkMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHZGj1%2FbtrmFoE21ef%2FDuqIGWJXUkah7KOP3kKkMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;456&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sendable 타입은 다른 Actor 사이에 값을 공유할 수 있는 타입입니다. 하나의 위치에서 다른 위치로 값을 복사하고 두 위치가 서로 간섭하지 않고 해당 값의 복사본을 안전하게 수정할 수 있을 때 Sendable 타입이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에도 언급한 대로 각각의 복사본은 독립적이므로 값 타입은 Sendable입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor 타입은 mutable state에 대한 접근을 동기화하므로 Sendable입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class는 잘 구현된 경우에만 Sendable입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 클래스와 모든 하위 클래스가 immutable data만 가지고 있는 경우 Sendable이라고 할 수 있습니다. 또는 클래스가 내부적으로 Lock과 같은 동기화를 수행해서 스레드로부터 안전한 경우 Sendable이 될 수 있습니다. 하지만 대부분의 경우 이를 만족하지 못하므로 Sendable이 아니며, 함수는 언제나 Sendable일 필요는 없기 때문에 Actor 간에 안전하게 전달할 수 있는 새로운 함수 타입이 있는데, 이는 잠시 후에 알아보도록 하겠습니다.&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;1590&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/benLJx/btrmGWH6B05/vK0HCtsMaDyyCLNPYoVCd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/benLJx/btrmGWH6B05/vK0HCtsMaDyyCLNPYoVCd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/benLJx/btrmGWH6B05/vK0HCtsMaDyyCLNPYoVCd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbenLJx%2FbtrmGWH6B05%2FvK0HCtsMaDyyCLNPYoVCd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1590&quot; height=&quot;868&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor(사실상 모든 concurrent code)는 주로 Sendable 타입으로 통신해야 합니다. Sendable 타입은 data races로부터 코드를 보호하며 이는 Swift가 정적으로 검사를 시작하는 프로퍼티이며, 위와 같이 Sendable이 아닌 것을 전달하는 경우에는 오류가 발생됩니다.&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;1590&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Znc2z/btrmCNMpavV/J995mf7dgwCKBifjTksqC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Znc2z/btrmCNMpavV/J995mf7dgwCKBifjTksqC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Znc2z/btrmCNMpavV/J995mf7dgwCKBifjTksqC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZnc2z%2FbtrmCNMpavV%2FJ995mf7dgwCKBifjTksqC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1590&quot; height=&quot;486&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입이 Sendable인건 어떻게 알 수 있을까요? Sendable을 프로토콜이고 이를 따르면 Sendable 타입이라고 할 수 있습니다. 그런 뒤 Swift는 타입이 Sendable인지 확인합니다. 위 코드에서 Book 구조체는 모든 프로퍼티가 Sendable 타입이라면 Sendable입니다.&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;1596&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EyK7b/btrmzK3xru5/AEZZR8vmxCc9qYSd4bh3p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EyK7b/btrmzK3xru5/AEZZR8vmxCc9qYSd4bh3p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EyK7b/btrmzK3xru5/AEZZR8vmxCc9qYSd4bh3p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEyK7b%2FbtrmzK3xru5%2FAEZZR8vmxCc9qYSd4bh3p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1596&quot; height=&quot;474&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Author 타입이 클래스라면 authors 프로퍼티는 Sendable이 아니게 되어 위와 같이 오류를 발생시킵니다.&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;1588&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OevzU/btrmFoZkVcG/YcDE7xAxpy4dye7XQUqdU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OevzU/btrmFoZkVcG/YcDE7xAxpy4dye7XQUqdU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OevzU/btrmFoZkVcG/YcDE7xAxpy4dye7XQUqdU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOevzU%2FbtrmFoZkVcG%2FYcDE7xAxpy4dye7XQUqdU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;410&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 타입의 경우 Sendable 여부는 제네릭 인수에 따라 달라질 수 있습니다. 적절할 때 Sendable을 전파할 수 있도록 조건을 달 수 있습니다. 위와 같이 Pair 타입은 인수가 모두 Sendable인 경우에만 Sendable이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 동시에 공유해도 안전한 타입에 Sendable 적합성을 도입하는 것이 좋다고 합니다. 그러한 타입을 Actor 안에서 사용하면 Swift가 Actor 간에 Sendable을 적용하기 시작하고 코드가 실행 준비됩니다.&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;1338&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFIo3e/btrmxhVhRVK/sLblcyUbPG1QFLkgAim2z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFIo3e/btrmxhVhRVK/sLblcyUbPG1QFLkgAim2z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFIo3e/btrmxhVhRVK/sLblcyUbPG1QFLkgAim2z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFIo3e%2FbtrmxhVhRVK%2FsLblcyUbPG1QFLkgAim2z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;634&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 자체는 Sendable이 될 수 있으며, 즉 Actor 간에 함숫값을 전달하는 것은 안전하다는 것을 의미합니다. 이는 data races를 방지하기 위해 클로저가 할 수 있는 작업을 제한하는 클로저의 경우 특히 중요하다?라고 하는데.. 이해가 잘 안 되네요... (This is particularly important for &quot;closures&quot; where it restricts what the &quot;closure&quot; can do to help prevent data races.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Sendable closure은 mutable 지역 변수를 캡처할 수 없습니다. 이는 지역 변수가 data races를 허용하기 때문입니다. 즉 클로저가 캡처 가능한 것은 Sendable 타입이며 클로저를 사용해서 Actor 간 non-Sendable 타입을 이동시킬 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 동기식 Sendable 클로저는 Actor isolated 하지 않습니다. 외부에서 Actor에게 코드가 실행될 수 있기 때문이죠.&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;1588&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cli0Ie/btrmykqPNTC/TtxPGCXOgB2ti2wwqdAx3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cli0Ie/btrmykqPNTC/TtxPGCXOgB2ti2wwqdAx3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cli0Ie/btrmykqPNTC/TtxPGCXOgB2ti2wwqdAx3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcli0Ie%2FbtrmykqPNTC%2FTtxPGCXOgB2ti2wwqdAx3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;888&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 지금까지 봐왔던 예들은 Sendable 클로저 아이디어를 사용하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;detached task를 생성하는 작업은 함수 타입에 @Sendable 키워드가 붙은 Sendable 함수를 사용했습니다. 이번 영상 시작 부분에서 값 타입 Counter를 만들던 예를 기억하시나요? 해당 예에서는 두 개의 다른 클로저에서 동일한 값을 동시에 수정하려고 했었죠. 이는 Sendable 지역 변수에 대한 data race를 발생시키는 것입니다.&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;1572&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eA3Vl0/btrmEjYn4gJ/h17IlR05oR2SDimyFT8hA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eA3Vl0/btrmEjYn4gJ/h17IlR05oR2SDimyFT8hA0/img.png&quot; data-alt=&quot; &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eA3Vl0/btrmEjYn4gJ/h17IlR05oR2SDimyFT8hA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeA3Vl0%2FbtrmEjYn4gJ%2Fh17IlR05oR2SDimyFT8hA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1572&quot; height=&quot;860&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;860&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;하지만 detached task에 대한 클로저는 Sendable 이므로 Swift는 오류를 발생시킵니다. Sendable 함수 타입은 동시 실행이 발생할 수 있는 위치를 나타내는 데 사용되므로 data races를 방지합니다.&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;1586&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oUwFC/btrmGh6JBbY/ksHXoJGQ3KZKiGqM3PrC80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oUwFC/btrmGh6JBbY/ksHXoJGQ3KZKiGqM3PrC80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oUwFC/btrmGh6JBbY/ksHXoJGQ3KZKiGqM3PrC80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoUwFC%2FbtrmGh6JBbY%2FksHXoJGQ3KZKiGqM3PrC80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1586&quot; height=&quot;870&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 예를 한 번 보겠습니다. detached task에 대한 클로저는 sendable 하므로 Actor에서 분리되면 안 된다는 것을 알고 있습니다. 따라서 상호작용은 비동기로 발생해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l3n3g/btrmGhyUqrc/YuLKOBvuQdvR3Xut4ULBkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l3n3g/btrmGhyUqrc/YuLKOBvuQdvR3Xut4ULBkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l3n3g/btrmGhyUqrc/YuLKOBvuQdvR3Xut4ULBkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl3n3g%2FbtrmGhyUqrc%2FYuLKOBvuQdvR3Xut4ULBkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1596&quot; height=&quot;878&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sendable 타입 및 클로저는 mutable state가 Actor 간에 공유되지 않고 동시에 수정할 수 없는지 확인해서 Actor isolation을 유지하는데 도움이 됩니다.&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;Main Actor&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 마지막으로 Main Actor라는 특별한 Actor에 대해 알아봅시다.&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;912&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdFUqg/btrmCCjX5Jn/mK3NTSayGshDLI7KyQ4gk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdFUqg/btrmCCjX5Jn/mK3NTSayGshDLI7KyQ4gk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdFUqg/btrmCCjX5Jn/mK3NTSayGshDLI7KyQ4gk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdFUqg%2FbtrmCCjX5Jn%2FmK3NTSayGshDLI7KyQ4gk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;912&quot; height=&quot;444&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 빌드할 땐 main 스레드를 많이 고려합니다. UI 렌더링이 발생하거나 사용자 이벤트를 처리하는 스레드가 main 스레드인데요, 모든 작업을 main 스레드에서 실행하고 싶지 않을 수 있습니다. 예를 들어 느린 입출력 작업이나 서버와 많은 작업을 처리하면 UI가 정지될 수 있죠. 따라서 main 스레드가 UI와 상호작용할 때는 오래 걸리는 작업은 main 스레드에서 빨리 빠져나오도록 해야 합니다.&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;1610&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daSclq/btrmDRVwcGd/l5ESRHL0LpTlBsKC7dn0QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daSclq/btrmDRVwcGd/l5ESRHL0LpTlBsKC7dn0QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daSclq/btrmDRVwcGd/l5ESRHL0LpTlBsKC7dn0QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaSclq%2FbtrmDRVwcGd%2Fl5ESRHL0LpTlBsKC7dn0QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1610&quot; height=&quot;890&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 가능하면 main 스레드에서 작업한 뒤 main 스레드에서 실행해야 하는 특정 작업이 있을 때마다 DispatchQueue.main.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;위 코드는 좀 친숙해 보이는데요, 사실 main 스레드와 상호작용하는 것은 Actor와 상호작용하는 것과 비슷합니다. 이미 main 스레드에서 실행 중임을 알고 있다면 UI 상태에 안전하게 접근하고 업데이트할 수 있습니다. main 스레드에서 실행하지 않는 경우 비동기로 상호작용해야 하죠. 이것이 바로 Actor가 작동하는 방식입니다.&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;1558&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y33dM/btrmEiZrre4/kKUkuWFmNpkZehpkaH1sNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y33dM/btrmEiZrre4/kKUkuWFmNpkZehpkaH1sNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y33dM/btrmEiZrre4/kKUkuWFmNpkZehpkaH1sNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy33dM%2FbtrmEiZrre4%2FkKUkuWFmNpkZehpkaH1sNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1558&quot; height=&quot;896&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainActor라고 부르는 main 스레드를 나타내는 특별한 Actor가 있습니다. 이는 일반 Actor와 다음 2가지가 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MainActor는 Main Dispatch Queue를 통해 모든 동기화를 수행합니다. 즉 런타임 관점에서 MainActor는 DispatchQueue.main을 사용해서 교체할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Main 스레드에 있어야 할 코드와 데이터가 여기저기 존재합니다. SwiftUI, AppKit, UIKit 및 시스템 프레임워크에 존재하죠&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 동시성을 사용하면 MainActor를 표시해서 main actor에서 실행되어야 한다고 선언할 수 있습니다. 위 코드에서 checkedOut() 메서드는 항상 MainActor에서 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainActor 외부에서 호출하는 경우 await 키워드를 사용해서 비동기적으로 수행되도록 해야 합니다. main 스레드에서 실행되어야 하는 코드를 MainActor에 있는 것으로 표시하면 DispatchQueue.main을 언제 사용해야 하는지 더 이상 고려할 필요가 없습니다. Swift가 해당 코드를 항상 main 스레드에서 실행되도록 하기 때문이죠.&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;1546&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ekVs/btrmEcZoKMU/1AWT3DRckp1HLtUMtdHhPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ekVs/btrmEcZoKMU/1AWT3DRckp1HLtUMtdHhPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ekVs/btrmEcZoKMU/1AWT3DRckp1HLtUMtdHhPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ekVs%2FbtrmEcZoKMU%2F1AWT3DRckp1HLtUMtdHhPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1546&quot; height=&quot;862&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입은 MainActor에도 위치시킬 수 있으므로 모든 멤버와 하위 클래스가 MainActor에 존재하게 됩니다. 이는 대부분 모든 것이 Main 스레드에서 실행되어야 하는 UI 관련 코드를 작성할 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfZ8SG/btrmEEuz4fg/3E5Maw0XSalANMVeGU4Ra1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfZ8SG/btrmEEuz4fg/3E5Maw0XSalANMVeGU4Ra1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfZ8SG/btrmEEuz4fg/3E5Maw0XSalANMVeGU4Ra1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfZ8SG%2FbtrmEEuz4fg%2F3E5Maw0XSalANMVeGU4Ra1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;561&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상에서 Actor isolation을 사용하고 실행을 동기적으로 실행하기 위해 Actor 외부에서 비동기 접근을 요구해서 Actor가 동시 접근으로부터 shared mutable state를 보호하는 방법을 알아봤습니다. 하지만 await를 사용할 땐 주의해야 한다는 점도 알아봤습니다. 값 타입과 Actor를 함께 사용하면 data race를 제거할 수 있었지만 참조 타입과 Actor를 함께 사용할 땐 Sendable 타입을 사용해야 한다는 것도 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 UI 관련 코드를 MainActor를 사용해서 항상 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;앱에서 Actor를 사용하는 자세한 방법은 아래 영상을 확인하라고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-hires-status=&quot;pending&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10194&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift concurrency: Update a sample app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor를 포함한 Swift의 동시성 모델 구현에 대해 자세히 알아보려면 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-hires-status=&quot;pending&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10254&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift concurrency: Behind the scenes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 Swift의 동시성 모델의 핵심 부분으로 async / await와 structured concurrency와 함께 동작해서 정확하고 효율적인 동시성 프로그램을 쉽게 구축할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p 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;Actor는 영상을 몇 번 보고 정리한 건데도 이해가 잘 안 되는 부분이 있는 어려운 개념인 듯합니다. 하지만 아주 중요한 내용이니... 더 공부해서 잘 사용할 수 있도록 해야겠네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>Apple/WWDC 2021</category>
      <category>2021</category>
      <category>actor</category>
      <category>concurrency</category>
      <category>IOS</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/269</guid>
      <comments>https://icksw.tistory.com/269#entry269comment</comments>
      <pubDate>Tue, 30 Nov 2021 23:00:10 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2019] Introducing Combine</title>
      <link>https://icksw.tistory.com/268</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 WWDC 2019의 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/722/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&quot;Introducing Combine&quot;&lt;/a&gt;이라는 영상을 보고 정리한 글을 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상은 2019년에 처음 공개된 Combine이라는 프레임워크를 소개하는 영상이었습니다.&lt;/p&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과 비슷한 역할을 하는 프레임워크로 RxSwift가 있는데, 그중 Combine 공부를 시작하기 전에 영상을 시청하게 되었네요. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine은 iOS 13.0 부터 지원하기 때문에 이전까지는 버전 문제로 선택되지 않는 경우가 있었는데 이제 iOS 15도 나온 만큼 많이 사용될 것 같습니다.&lt;/p&gt;
&lt;h1&gt;Introducing Combine&lt;/h1&gt;
&lt;p data-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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;872&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5uu2v/btriuVtz2AM/etU7TIKbt9k3jWfLA7uAt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5uu2v/btriuVtz2AM/etU7TIKbt9k3jWfLA7uAt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5uu2v/btriuVtz2AM/etU7TIKbt9k3jWfLA7uAt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5uu2v%2FbtriuVtz2AM%2FetU7TIKbt9k3jWfLA7uAt1%2Fimg.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;872&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;850&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pLERi/btriwx6O7So/wDPQCt4TzBSPM7xK9oi0Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pLERi/btriwx6O7So/wDPQCt4TzBSPM7xK9oi0Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pLERi/btriwx6O7So/wDPQCt4TzBSPM7xK9oi0Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpLERi%2Fbtriwx6O7So%2FwDPQCt4TzBSPM7xK9oi0Sk%2Fimg.png&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;850&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사용자 이름을 입력하게 된다면 Target / Action을 통해 사용자 입력에 대한 알림을 받습니다. 그리고 입력한 이름이 유효한 이름인지 서버를 통해 확인하기 위해 네트워크 통신을 하는데요, 이때 Timer를 사용해서 사용자의 네트워크 요청이 서버에 과부하를 주지 않기 위해 사용자 입력이 멈출 때까지 기다립니다.&amp;nbsp;그리고 KVO 등을 통해 비동기 작업에 대한 진행률 업데이트도 수신할 수 있습니다.&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-origin-width=&quot;496&quot; data-origin-height=&quot;376&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JSHhK/btrivP0UEgK/nJ3Ms0hpbRDhpg1nESOA11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JSHhK/btrivP0UEgK/nJ3Ms0hpbRDhpg1nESOA11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JSHhK/btrivP0UEgK/nJ3Ms0hpbRDhpg1nESOA11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJSHhK%2FbtrivP0UEgK%2FnJ3Ms0hpbRDhpg1nESOA11%2Fimg.png&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;376&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이름과 암호를 입력하면서 서버와 통신해서 유효한 값인지 체크해줘야하고, 해당 체크의 결과대로 UI도 업데이트해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;688&quot; width=&quot;413&quot; height=&quot;423&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/16Cl5/btrirw9mCft/ZcCBqxpGvlVzpkXAlxWG20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/16Cl5/btrirw9mCft/ZcCBqxpGvlVzpkXAlxWG20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/16Cl5/btrirw9mCft/ZcCBqxpGvlVzpkXAlxWG20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F16Cl5%2Fbtrirw9mCft%2FZcCBqxpGvlVzpkXAlxWG20%2Fimg.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;688&quot; width=&quot;413&quot; height=&quot;423&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에 존재했던 비동기 인터페이스는 위와 같은 것들이 있었고, 각각 다른 사용법을 가지고 있었습니다. 이것들을 함께 사용할 때 어려움이 발생했고 Combine을 만들 때 애플은 이것들을 대체하는 것이 아닌 공통점을 찾으려고 했다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;334&quot; width=&quot;748&quot; height=&quot;226&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OLCJ7/btrit4YRhDT/g91XJNsj49QQEWkPofgW80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OLCJ7/btrit4YRhDT/g91XJNsj49QQEWkPofgW80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OLCJ7/btrit4YRhDT/g91XJNsj49QQEWkPofgW80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOLCJ7%2Fbtrit4YRhDT%2Fg91XJNsj49QQEWkPofgW80%2Fimg.png&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;334&quot; width=&quot;748&quot; height=&quot;226&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 정의한 Combine은 위와 같이 &quot;시간 경과에 따른 값 처리를 위한 통합적이고 선언적인 API!&quot;라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Combine Features&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;576&quot; width=&quot;393&quot; height=&quot;432&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VUSPO/btritN37Ngg/04IExEOYgotSojzaxo29m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VUSPO/btritN37Ngg/04IExEOYgotSojzaxo29m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VUSPO/btritN37Ngg/04IExEOYgotSojzaxo29m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVUSPO%2FbtritN37Ngg%2F04IExEOYgotSojzaxo29m1%2Fimg.png&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;576&quot; width=&quot;393&quot; height=&quot;432&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine의 특징을 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Combine은 Swift용으로 만들어져서 Generic과 같은 Swift 기능을 활용할 수 있습니다. Generic을 사용하면 코드의 양도 줄일 수 있고 하나의 알고리즘을 다양한 곳에서 사용할 수 있다는 말도 됩니다.&lt;/li&gt;
&lt;li&gt;Combine은 Type safe 하므로 런타임이 아닌 컴파일 시간에 오류를 잡을 수 있다고 합니다.&lt;/li&gt;
&lt;li&gt;Combine은 Composition first(구성이 먼저?)라서 핵심 개념이 간단하고 이해하기 쉽지만 이들을 사용하면 큰 효과를 볼 수 있다고 하네요.&lt;/li&gt;
&lt;li&gt;Combine은 Request driven(요청 기반)이므로 앱의 메모리 사용량과 성능을 잘 관리할 수 있다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Combine Key Concepts&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;492&quot; width=&quot;336&quot; height=&quot;360&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kLNJS/btriwwtiXK1/QyrP3NzK3A5ZT27veP0I1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kLNJS/btriwwtiXK1/QyrP3NzK3A5ZT27veP0I1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kLNJS/btriwwtiXK1/QyrP3NzK3A5ZT27veP0I1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkLNJS%2FbtriwwtiXK1%2FQyrP3NzK3A5ZT27veP0I1K%2Fimg.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;492&quot; width=&quot;336&quot; height=&quot;360&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine의 핵심 개념은 위의 Publisher, Subscriber, Operator 3가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 살펴보겠습니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Publisher&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;498&quot; width=&quot;662&quot; height=&quot;371&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxPqsU/btrisMdneu2/oB7D15QMQ7bnsuEqUawxzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxPqsU/btrisMdneu2/oB7D15QMQ7bnsuEqUawxzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxPqsU/btrisMdneu2/oB7D15QMQ7bnsuEqUawxzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxPqsU%2FbtrisMdneu2%2FoB7D15QMQ7bnsuEqUawxzk%2Fimg.png&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;498&quot; width=&quot;662&quot; height=&quot;371&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher는 Combine API의 선언 부분이라고 합니다. 즉 값과 오류가 생성되는 방식을 알려줍니다. 이름과 다르게 Publisher가 반드시 뭔가를 생산하는 것은 아니라고 하네요. 구조체를 사용하기 때문에 Value 타입이며 Subscriber 등록도 허용한다고 합니다.&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-origin-width=&quot;1530&quot; data-origin-height=&quot;576&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D4fqb/btriww09QZt/TOov2lYuJgN0cJ5UyTNmKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D4fqb/btriww09QZt/TOov2lYuJgN0cJ5UyTNmKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D4fqb/btriww09QZt/TOov2lYuJgN0cJ5UyTNmKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD4fqb%2Fbtriww09QZt%2FTOov2lYuJgN0cJ5UyTNmKk%2Fimg.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;576&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 Publisher의 Protocol입니다. 두 개의 associatedtype이 있으며 Output은 생성하는 값의 종류, Failure는 실패했을 때 발생하는 에러입니다. 그리고 Publisher의 핵심 기능인 subscribe 함수가 있네요. 프로토콜에서 볼 수 있듯 매개변수인 subscriber의 input은 publisher의 Output과 동일해야 하고 subscriber의 failure는 publisher의 failure와 일치해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Publisher가 에러를 생성할 수 없는 경우엔 Error 타입 대신 Never 타입을 사용할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Never가 뭐지? 해서 &lt;a href=&quot;https://developer.apple.com/documentation/swift/never&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에서 찾아봤는데 &quot;정상적으로 반환하지 않는 함수의 반환 타입, 즉 값이 없는 타입&quot;이라고 하네요.&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-origin-width=&quot;1516&quot; data-origin-height=&quot;528&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7lkjj/btrit5csx0b/UoTorBxTdHWk1KEFPQxSmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7lkjj/btrit5csx0b/UoTorBxTdHWk1KEFPQxSmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7lkjj/btrit5csx0b/UoTorBxTdHWk1KEFPQxSmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7lkjj%2Fbtrit5csx0b%2FUoTorBxTdHWk1KEFPQxSmk%2Fimg.png&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;528&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예를 보면 위와 같습니다. 위 코드는 NotificationCenter를 위한 새로운 Publisher입니다. Output은 Notification이고 Failure는 Never 타입이네요. Center, name, object로 생성되는 것도 알 수 있습니다. 아까도 말했듯 Combine은 기존의 API들을 대체하는 것이 아닌 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Subscriber&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;446&quot; width=&quot;458&quot; height=&quot;287&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YobHx/btriwxMx0c4/7i1Ggk7pej60LQyRUgTFKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YobHx/btriwxMx0c4/7i1Ggk7pej60LQyRUgTFKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YobHx/btriwxMx0c4/7i1Ggk7pej60LQyRUgTFKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYobHx%2FbtriwxMx0c4%2F7i1Ggk7pej60LQyRUgTFKk%2Fimg.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;446&quot; width=&quot;458&quot; height=&quot;287&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Subscriber입니다. Subscriber는 Publisher와 반대되는 개념으로 Publisher에게 값을 받습니다. Subscriber는 값을 받게 되면 뭔가를 처리하고 상태를 변경하기 때문에 Swift의 class와 동일한 reference type을 사용합니다.&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-origin-width=&quot;1540&quot; data-origin-height=&quot;612&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/do9zHU/btriwwmzD7I/YJVgVW7Ly1Tk8PKC1K5Ek1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/do9zHU/btriwwmzD7I/YJVgVW7Ly1Tk8PKC1K5Ek1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/do9zHU/btriwwmzD7I/YJVgVW7Ly1Tk8PKC1K5Ek1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdo9zHU%2FbtriwwmzD7I%2FYJVgVW7Ly1Tk8PKC1K5Ek1%2Fimg.png&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;612&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scbscriber의 protocol도 한 번 보겠습니다. 아까와 동일하게 2개의 associatedtype이 있습니다. Subscriber도 마찬가지로 에러를 수신할 수 없다면 Never 타입을 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher와 다르게 3개의 함수가 있습니다. 모두 이름은 receive이며 매개변수를 살펴보면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subscription
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subscriber가 Publisher에서 Subscriber에게 전달되는 데이터를 제어하는 방법입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Input
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Upstream에서 전달되는 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;completion
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subscribers.Completion &amp;lt;Failure&amp;gt;이라는 타입에서 볼 수 있듯 성공 혹은 실패할 수 있는 Completion을 수신할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;624&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bg3Ha/btriuhp38qK/q5RkxAuzGuZ90DGzmKr9A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bg3Ha/btriuhp38qK/q5RkxAuzGuZ90DGzmKr9A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bg3Ha/btriuhp38qK/q5RkxAuzGuZ90DGzmKr9A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBg3Ha%2Fbtriuhp38qK%2Fq5RkxAuzGuZ90DGzmKr9A1%2Fimg.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;624&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber도 예제를 보면 위와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 것을 Assign(할당)이라고 하며 클래스입니다. 얘가 하는 일은 Upstream에서 input을 받으면 해당 객체의 프로퍼티에 기록하는 것이라고 하네요. Swift에서는 프로퍼티 값을 작성할 때 오류를 처리할 방법이 없기 때문에 Failure를 Never로 설정해준 것도 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;How Publisher, Subscriber fit together&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이렇게 알아본 Publisher, Subscriber가 어떻게 동작하는지 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;962&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brbYUQ/btrirxUI906/3wVHbHSe6zUK2e9a0FQ0SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brbYUQ/btrirxUI906/3wVHbHSe6zUK2e9a0FQ0SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brbYUQ/btrirxUI906/3wVHbHSe6zUK2e9a0FQ0SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrbYUQ%2FbtrirxUI906%2F3wVHbHSe6zUK2e9a0FQ0SK%2Fimg.png&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;962&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController와 같은 객체에 Subscriber가 존재할 수 있고, 이를 갖고 있는 객체는 Subscriber와 함께 Publisher의 subscribe 함수를 호출해서 연결해야 하는 책임을 갖고 있습니다.&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-origin-width=&quot;1788&quot; data-origin-height=&quot;926&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J6ZWr/btritJ1FoJT/6kEkPFzPU0EnKtHhpBEolK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J6ZWr/btritJ1FoJT/6kEkPFzPU0EnKtHhpBEolK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J6ZWr/btritJ1FoJT/6kEkPFzPU0EnKtHhpBEolK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ6ZWr%2FbtritJ1FoJT%2F6kEkPFzPU0EnKtHhpBEolK%2Fimg.png&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;926&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에서 Publisher는 Subscriber에게 subscription을 보내서 Subscriber가 Publisher에게 몇 번 혹은 무제한으로 요청을 할 수 있게 합니다.&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-origin-width=&quot;1768&quot; data-origin-height=&quot;948&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkcooy/btrioHjmwZi/jjP3Cj5wdqKVtsyMNVQ3y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkcooy/btrioHjmwZi/jjP3Cj5wdqKVtsyMNVQ3y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkcooy/btrioHjmwZi/jjP3Cj5wdqKVtsyMNVQ3y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkcooy%2FbtrioHjmwZi%2FjjP3Cj5wdqKVtsyMNVQ3y0%2Fimg.png&quot; data-origin-width=&quot;1768&quot; data-origin-height=&quot;948&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 Publisher가 값들을 Subscriber에게 자유롭게 보낼 수 있게 되며 Publisher가 유한한 경우 completion 또는 에러를 보내게 됩니다. 여기서 중요한 것은 하나의 subscription에는 0개 이상의 값과 하나의 completion이 존재한다는 것입니다.&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-origin-width=&quot;1818&quot; data-origin-height=&quot;778&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/casEXG/btrivDlW7mQ/myCPHXGjnakvq3auZUkML0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/casEXG/btrivDlW7mQ/myCPHXGjnakvq3auZUkML0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/casEXG/btrivDlW7mQ/myCPHXGjnakvq3auZUkML0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcasEXG%2FbtrivDlW7mQ%2FmyCPHXGjnakvq3auZUkML0%2Fimg.png&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;778&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 마법사 학교에 학생 등록하는 예로 돌아가 보겠습니다. Wizard라는 모델 객체가 있고 grade라는 값을 갖고 있다고 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;merlin이라는 애는 5학년이라고 정의합니다. 여기서 하고 싶은 것은 졸업하는 학생들에 대한 알림을 듣고 졸업하면 Wizard 객체들의 값을 업데이트하는 일입니다. 이를 위해 NotificationCenter의 Publisher와 Subscribers Assign 객체를 만듭니다. 이렇게 하면 알림을 받을 때 merlin의 grade 값을 새로 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이렇게 하면 컴파일이 되지 않는 것을 발견할 수 있는데요, 이유는 타입이 일치하지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;790&quot; width=&quot;375&quot; height=&quot;558&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4rAgZ/btriwHIlW9P/IHsGxC486Vu8hIlr4ZAKfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4rAgZ/btriwHIlW9P/IHsGxC486Vu8hIlr4ZAKfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4rAgZ/btriwHIlW9P/IHsGxC486Vu8hIlr4ZAKfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4rAgZ%2FbtriwHIlW9P%2FIHsGxC486Vu8hIlr4ZAKfk%2Fimg.png&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;790&quot; width=&quot;375&quot; height=&quot;558&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 보면 NotificationCenter는 Notification을 보내지만 Assign이 grade라는 Int 타입에 값을 쓰려면 Int가 필요하겠죠. 따라서 이를 변환해줄 무언가를 중간에 만들어줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 역할을 하는 것이 Operator입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Operator&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;764&quot; width=&quot;548&quot; height=&quot;403&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JHozR/btriwxFNKZJ/zQoA7oI3hHdbwHV5I7Hoe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JHozR/btriwxFNKZJ/zQoA7oI3hHdbwHV5I7Hoe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JHozR/btriwxFNKZJ/zQoA7oI3hHdbwHV5I7Hoe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJHozR%2FbtriwxFNKZJ%2FzQoA7oI3hHdbwHV5I7Hoe0%2Fimg.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;764&quot; width=&quot;548&quot; height=&quot;403&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operator는 Publisher 프로토콜을 채택하므로 Publisher와 동일하게 뭔가를 생성합니다. 선언적이므로 값 타입이며 Operator는 값을 변경하거나, 추가, 제거와 같은 다양한 동작을 처리하는 일을 합니다. 그리고 먼저 실행되는 Upstream Publisher를 subscribe 하는 일, &amp;nbsp;이후에 실행될 Downstream Subscriber에게 결과를 보내는 일을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Upstream, Downstream이라는 용어는 단순하게 코드에서 위쪽에 존재하면 Upstream, 아래쪽에 존재하면 Downstream이라고 이해하면 될 듯싶었습니다. 즉 먼저 실행되는 코드를 Upstream, 이후에 실행되는 코드를 Downstream이라고 이해했습니다.&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-origin-width=&quot;1830&quot; data-origin-height=&quot;702&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/np5rR/btrin1hYvIp/BQNiYCmVc8UKF9xRuBaw9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/np5rR/btrin1hYvIp/BQNiYCmVc8UKF9xRuBaw9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/np5rR/btrin1hYvIp/BQNiYCmVc8UKF9xRuBaw9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnp5rR%2Fbtrin1hYvIp%2FBQNiYCmVc8UKF9xRuBaw9k%2Fimg.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;702&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operator의 예를 하나 보겠습니다. 위의 코드에서 사용된 연산자 이름은 Map입니다. Map은 upstream과 upstream의 output을 자체 output으로 변환하는 transform이 존재하는 구조체입니다. Map은 실패를 생성하지 않기 때문에 Upstream의 실패 타입을 미러링 하게 됩니다.&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-origin-width=&quot;476&quot; data-origin-height=&quot;786&quot; width=&quot;310&quot; height=&quot;512&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HIWIO/btriq6iSLke/IY3RPELQ37x8lU7jgNtR01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HIWIO/btriq6iSLke/IY3RPELQ37x8lU7jgNtR01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HIWIO/btriq6iSLke/IY3RPELQ37x8lU7jgNtR01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHIWIO%2Fbtriq6iSLke%2FIY3RPELQ37x8lU7jgNtR01%2Fimg.png&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;786&quot; width=&quot;310&quot; height=&quot;512&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 봤던 문제를 해결하기 위해 이제 중간에 Map이라는 Operator가 들어옵니다. 얘가 어떤 역할을 하는지 살펴보겠습니다.&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-origin-width=&quot;1554&quot; data-origin-height=&quot;598&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mITyE/btriwxMAnD2/GcOASgnOHpqPebYGHkr930/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mITyE/btriwxMAnD2/GcOASgnOHpqPebYGHkr930/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mITyE/btriwxMAnD2/GcOASgnOHpqPebYGHkr930/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmITyE%2FbtriwxMAnD2%2FGcOASgnOHpqPebYGHkr930%2Fimg.png&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;598&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 코드에서 converter라는 녀석이 추가되었습니다. Converter의 클로저는 알림을 수신하고 NewGrade라는 userInfo를 가지고 정수이면 클로저에서 반환합니다. 이렇게 하면 클로저의 결과가 정수가 되어 Subscriber에 연결할 수 있게 됩니다. 아까 발생했던 컴파일 오류도 해결되는 것을 볼 수 있어요.&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-origin-width=&quot;1578&quot; data-origin-height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/reXwU/btrirxNYMOr/GCXWm9ly4IG0WhqJnEFN20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/reXwU/btrirxNYMOr/GCXWm9ly4IG0WhqJnEFN20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/reXwU/btrirxNYMOr/GCXWm9ly4IG0WhqJnEFN20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FreXwU%2FbtrirxNYMOr%2FGCXWm9ly4IG0WhqJnEFN20%2Fimg.png&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 좀 더 응용해서 위와 같은 코드도 작성할 수 있게 됩니다. 위와 같이 만들면 모든 Publisher가 사용할 수 있는 함수가 됩니다. 이렇게 하면 self를 사용할 수 있게 되므로 코드가 좀 더 간단해집니다.&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-origin-width=&quot;1536&quot; data-origin-height=&quot;320&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmTFfh/btritNiNgMD/kmFVmhPDDGQB6iJkq1hWrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmTFfh/btritNiNgMD/kmFVmhPDDGQB6iJkq1hWrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmTFfh/btritNiNgMD/kmFVmhPDDGQB6iJkq1hWrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmTFfh%2FbtritNiNgMD%2FkmFVmhPDDGQB6iJkq1hWrk%2Fimg.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;320&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 만든 것을 사용해보면 위와 같습니다. 알림을 받게 되면 앞에서 본 것과 동일한 클로저를 사용해서 매핑한 뒤 merlin의 grade 프로퍼티에 할당합니다. 이렇게 작성하고 보면 코드가 매우 선형적이고 쉬운 흐름을 갖게 되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Assign은 cancelable 항목을 반환하는데 Cancelation도 Combine에 포함되어있습니다. 따라서 Cancelation을 사용해서 Publisher, Subscriber를 조기에 해제할 수도 있다고 합니다.&lt;/p&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 사용 방법의 핵심입니다. 각 단계는 다음 명령어를 설명하며, 첫 번째 Publisher에서 일련의 Operator를 거쳐서 Subscriber로 값을 반환하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Declarative Operator API&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nusSA/btrit4EA67J/hRoei3L6YRfg5dJ3dUWDWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nusSA/btrit4EA67J/hRoei3L6YRfg5dJ3dUWDWK/img.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;752&quot; width=&quot;434&quot; height=&quot;390&quot; style=&quot;width: 37.318137090485116%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nusSA/btrit4EA67J/hRoei3L6YRfg5dJ3dUWDWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnusSA%2Fbtrit4EA67J%2FhRoei3L6YRfg5dJ3dUWDWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;836&quot; height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AXDP2/btripFsiaaO/ePsIKvcUGRqjC3Kx1Pkx8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AXDP2/btripFsiaaO/ePsIKvcUGRqjC3Kx1Pkx8K/img.png&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;968&quot; style=&quot;width: 61.51907221184047%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AXDP2/btripFsiaaO/ePsIKvcUGRqjC3Kx1Pkx8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAXDP2%2FbtripFsiaaO%2FePsIKvcUGRqjC3Kx1Pkx8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1774&quot; height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine에는 이미 위와 같이 많은 Operator를 가지고 있고 이들을 Declarative Operative API라고 부른다고 합니다. 잘 보면 Map, Reduce, Filter도 있고 오류 처리를 위한 Operator도 있습니다. 스레드나 큐에서 이동, from 루프 디스패치 큐, 타이머 등등 정말 많은 Operator가 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Operator가 너무 많기 때문에 이를 잘 활용하는 방법을 생각하는 것이 어려울 수 있을 듯합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Try composition first&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;380&quot; width=&quot;597&quot; height=&quot;225&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vcT3N/btriujO5Uwv/8mOjEKkEdJKptQQOnL91m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vcT3N/btriujO5Uwv/8mOjEKkEdJKptQQOnL91m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vcT3N/btriujO5Uwv/8mOjEKkEdJKptQQOnL91m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvcT3N%2FbtriujO5Uwv%2F8mOjEKkEdJKptQQOnL91m0%2Fimg.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;380&quot; width=&quot;597&quot; height=&quot;225&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Operator가 많기 때문에 애플에서 권장하는 것은 Combine에 대한 핵심 디자인 원칙인 Composition으로 돌아가는 것이라고 합니다.&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;많은 작업을 모두 수행하는 몇 개의 Operator를 제공하기보다는 간단한 작업을 수행하는 Operator를 많이 제공해해서 이해하기 쉽게 만들었다는 의미로, 개발자들이 쉽게 이해할 수 있도록 Swift Collection API에서 이름을 많이 따왔다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;826&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwAcpd/btriuktHSmr/wLW3bkHkhdwMRA4T2cfjq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwAcpd/btriuktHSmr/wLW3bkHkhdwMRA4T2cfjq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwAcpd/btriuktHSmr/wLW3bkHkhdwMRA4T2cfjq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwAcpd%2FbtriuktHSmr%2FwLW3bkHkhdwMRA4T2cfjq1%2Fimg.png&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;826&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 보면 왼쪽에는 Synchronous(동기) API가 있고 오른쪽에는 Asynchronous(비동기) API가 있습니다. 그리고 위쪽에는 단일 값, 아래쪽에는 여러 개의 값이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서 하나의 정수를 동기적으로 처리할 경우엔 Int를 사용하면 되지만 Combine을 사용해서 비동기적으로 사용하려면 Future를 사용하면 된다고 하네요. 또한 여러개의 값을 동기로 사용할 때는 Array, 비동기로 사용할 거면 Publisher를 사용하면 된다고 합니다.&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-origin-width=&quot;1544&quot; data-origin-height=&quot;358&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNbWzY/btriCEL7s8z/IgCZ6EvZnrIY0d8szTA2GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNbWzY/btriCEL7s8z/IgCZ6EvZnrIY0d8szTA2GK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNbWzY/btriCEL7s8z/IgCZ6EvZnrIY0d8szTA2GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNbWzY%2FbtriCEL7s8z%2FIgCZ6EvZnrIY0d8szTA2GK%2Fimg.png&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;358&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 하나 보겠습니다. 위의 코드를 보면 map에서 publisher에서 전달된 값이 Int 값이 아니라면 0을 저장하는 코드입니다. 그런데 값 자체가 이상한 건데 0이라는 값이 저장되는 게 마음에 들지 않기 때문에 nil을 반환할 수 있도록 수정하고 싶습니다.&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-origin-width=&quot;1558&quot; data-origin-height=&quot;394&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmRJiJ/btriBBI91fk/8yU5beHiMm1EWcEzKOCu51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmRJiJ/btriBBI91fk/8yU5beHiMm1EWcEzKOCu51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmRJiJ/btriBBI91fk/8yU5beHiMm1EWcEzKOCu51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmRJiJ%2FbtriBBI91fk%2F8yU5beHiMm1EWcEzKOCu51%2Fimg.png&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;394&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용할 수 있는 것은 Swift 4.1에 도입된 compactMap입니다. compactMap도 Combine에서 사용할 수 있기 때문에 위와 같이 사용해주면 됩니다. 그럼 이제 코드는 아까와 다르게 Int 값이 아닌 값은 nil을 downstream에 반환해줍니다.&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-origin-width=&quot;1534&quot; data-origin-height=&quot;408&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sLja0/btriDk7CaOO/Eu50ktflPCQNU6F4O8xCak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sLja0/btriDk7CaOO/Eu50ktflPCQNU6F4O8xCak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sLja0/btriDk7CaOO/Eu50ktflPCQNU6F4O8xCak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsLja0%2FbtriDk7CaOO%2FEu50ktflPCQNU6F4O8xCak%2Fimg.png&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;408&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Swift에서 자주 사용했던 filter도 사용해보겠습니다. Array에서 사용하던 filter와 마찬가지로 위와 같이 사용하면 upstream에서 전달된 값이 5 이상인 값만 downstream으로 전달해줍니다.&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-origin-width=&quot;1528&quot; data-origin-height=&quot;468&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DgFHU/btriFioiqCC/ONKU7u2pLvmk6PDJ88fDIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DgFHU/btriFioiqCC/ONKU7u2pLvmk6PDJ88fDIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DgFHU/btriFioiqCC/ONKU7u2pLvmk6PDJ88fDIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDgFHU%2FbtriFioiqCC%2FONKU7u2pLvmk6PDJ88fDIk%2Fimg.png&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;468&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 prefix라는 Operator를 사용해봅니다. 위 코드에서 .prefix(3)은 위 코드가 반복될 때 3번까지만 값을 수신하라는 의미가 됩니다. 즉 3번까지만 전달된 값을 downstream으로 전달하라는 말이 됩니다.&lt;/p&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 타입이고 5 이상이며 수신받은 횟수가 3회 이하일 때만 merlin의 grade 프로퍼티에 전달된 값을 저장하는 코드가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Combining Publishers&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;376&quot; width=&quot;499&quot; height=&quot;286&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m8jCQ/btriDxeVU8r/Is511cWAZp1L5qVRPJgBg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m8jCQ/btriDxeVU8r/Is511cWAZp1L5qVRPJgBg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m8jCQ/btriDxeVU8r/Is511cWAZp1L5qVRPJgBg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm8jCQ%2FbtriDxeVU8r%2FIs511cWAZp1L5qVRPJgBg1%2Fimg.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;376&quot; width=&quot;499&quot; height=&quot;286&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 Map, Filter는 주로 동기 작업을 위한 API 였습니다. 하지만 Combine은 비동기 작업을 할 때 빛을 발하기 때문에 이를 위한 Zip, CombineLatest라는 Operator도 살펴보겠습니다.&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;Zip&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;1048&quot; width=&quot;254&quot; height=&quot;442&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYhPeG/btriAHC4akE/JSvrh1xbrPRoj2eTIgdUpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYhPeG/btriAHC4akE/JSvrh1xbrPRoj2eTIgdUpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYhPeG/btriAHC4akE/JSvrh1xbrPRoj2eTIgdUpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYhPeG%2FbtriAHC4akE%2FJSvrh1xbrPRoj2eTIgdUpK%2Fimg.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;1048&quot; width=&quot;254&quot; height=&quot;442&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 예를 들던 마법사 계정 만들기 앱에서 이번에는 지팡이를 생성하는 단계를 보겠습니다. 지팡이를 만들기 위해서는 3개의 오래 걸리는 비동기 작업이 완료되어야 한다고 하네요. 따라서 3개의 작업이 모두 완료될 때 continue 버튼을 활성화하고 싶다고 합니다. 이럴 때 zip을 사용하면 된다고 합니다!&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-origin-width=&quot;1502&quot; data-origin-height=&quot;832&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn1jPk/btriz7WyPYK/EfbL6Bsn2qxK2NcseHW4M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn1jPk/btriz7WyPYK/EfbL6Bsn2qxK2NcseHW4M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn1jPk/btriz7WyPYK/EfbL6Bsn2qxK2NcseHW4M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn1jPk%2Fbtriz7WyPYK%2FEfbL6Bsn2qxK2NcseHW4M0%2Fimg.png&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;832&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zip은 여러 개의 upstream input을 하나의 tuple로 변환해서 downstream으로 전달합니다. 이때 downstream으로 전달하기 위해서는 tuple의 모든 값이 입력되어야 하므로 upstream의 모든 input이 전달된 후에 downstream으로 tuple을 전달할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림에서 보면 (A, 1)이라는 튜플이 subscriber로 전달되는데, A, 1을 모두 받은 뒤에 이를 tuple로 만들어서 subscriber로 전달한다는 의미입니다. A 혹은 1 중 하나라도 받지 못하면 subscriber는 아무것도 전달받지 못하게 되겠죠?&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-origin-width=&quot;1422&quot; data-origin-height=&quot;828&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d87n4y/btriz0wmCay/KhNLWliVlIK6JOeM2z4gEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d87n4y/btriz0wmCay/KhNLWliVlIK6JOeM2z4gEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d87n4y/btriz0wmCay/KhNLWliVlIK6JOeM2z4gEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd87n4y%2Fbtriz0wmCay%2FKhNLWliVlIK6JOeM2z4gEk%2Fimg.png&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;828&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 아까 지팡이 만들던 예에 적용해보면 위와 같습니다. 3개의 upstream이 필요한 Zip을 사용해서 3개의 비동기 작업이 완료되어야 downstream으로 값을 전달할 수 있습니다. 모두 완료되면 continueButton의 isEnabled 값에 true가 저장되므로 버튼이 활성화됩니다! 이렇게 다음 작업으로 넘어갈 수 있게 됩니다!&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-origin-width=&quot;490&quot; data-origin-height=&quot;884&quot; width=&quot;312&quot; height=&quot;563&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC0tPJ/btriCFj0G7m/b0FF2p0Q4CdcrfM2VJv1m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC0tPJ/btriCFj0G7m/b0FF2p0Q4CdcrfM2VJv1m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC0tPJ/btriCFj0G7m/b0FF2p0Q4CdcrfM2VJv1m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC0tPJ%2FbtriCFj0G7m%2Fb0FF2p0Q4CdcrfM2VJv1m1%2Fimg.png&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;884&quot; width=&quot;312&quot; height=&quot;563&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 작업은 지팡이를 가지고 놀기 전에 숙지해야 할 규칙들에 동의해야 하는 작업이라고 합니다.   Play 버튼이 활성화되기 전에 세 개의 스위치를 모두 활성화해야 하고, 하나라도 비활성화된 다면 play 버튼도 비활성화해야 합니다. 이럴 때 사용할 수 있는 것이 CombineLatest입니다.&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-origin-width=&quot;1528&quot; data-origin-height=&quot;820&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xPGzH/btriBZ3Y2m2/kugWyMMVU3Z3qf4RlQU6A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xPGzH/btriBZ3Y2m2/kugWyMMVU3Z3qf4RlQU6A0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xPGzH/btriBZ3Y2m2/kugWyMMVU3Z3qf4RlQU6A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxPGzH%2FbtriBZ3Y2m2%2FkugWyMMVU3Z3qf4RlQU6A0%2Fimg.png&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;820&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upstream에서 전달받은 값을 하나의 값으로 변환하는 것은 Zip과 동일하지만 upstream의 값이 변경될 때마다 downstream으로 값을 전달합니다. 이렇게 해서 downstream은 upstream의 가장 최신 정보를 얻을 수 있습니다.&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-origin-width=&quot;1492&quot; data-origin-height=&quot;890&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRpyNr/btriArUPy9F/iJ63HoDJ4tyHvyRjnowqu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRpyNr/btriArUPy9F/iJ63HoDJ4tyHvyRjnowqu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRpyNr/btriArUPy9F/iJ63HoDJ4tyHvyRjnowqu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRpyNr%2FbtriArUPy9F%2FiJ63HoDJ4tyHvyRjnowqu1%2Fimg.png&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;890&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 코드에 활용하면 위와 같습니다. 3개의 upstream을 사용하는 CombineLatest를 사용해서 3개의 스위치가 모두 활성화되었을 때만 true를 downstream에 전달해주고 다른 경우에는 모두 false를 전달하게 됩니다. 이렇게 하면 Play 버튼의 활성화 여부를 쉽게 관리할 수 있게 됩니다.&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-origin-width=&quot;1066&quot; data-origin-height=&quot;514&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nPwLW/btriFiaKU3K/T9DIzAiIK1WF9bv7BYwvw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nPwLW/btriFiaKU3K/T9DIzAiIK1WF9bv7BYwvw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nPwLW/btriFiaKU3K/T9DIzAiIK1WF9bv7BYwvw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnPwLW%2FbtriFiaKU3K%2FT9DIzAiIK1WF9bv7BYwvw1%2Fimg.png&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;514&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 영상에서는 Combine에 대한 소개를 마칩니다. Combine을 사용하기 위해서 기존 코드를 모두 변환할 필요는 없고 이번 영상에서 봤듯 NotificationCenter의 확장된 형태로 사용할 수 있었습니다. 그리고 간단하게 여러 Operator의 사용 예도 봤었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 작업 중 가장 많이 하는 작업 중 하나는 네트워크 작업일 텐데요, URLSession을 사용해서 데이터를 수신한 뒤 JSON Decoder를 사용해서 데이터를 변환하는 경우에 사용할 수 있는 decode Operator도 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세한 내용은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/721&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Combine in Practice&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이번 영상은 마무리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상에서는 Combine의 가장 중요한 Publisher, Subscriber, Operator가 무엇인지 알아봤고, 그중 몇 가지 Operator에 대해서도 알아봤습니다. Combine을 공부하기 전에 핵심 개념을 알아보기에 좋은 영상이었던 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘 WWDC 영상인 &quot;Introducing Combine&quot;는 여기까지 정리하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Apple/WWDC 2019</category>
      <category>2019</category>
      <category>Asyncronous</category>
      <category>Combine</category>
      <category>IOS</category>
      <category>operator</category>
      <category>Publisher</category>
      <category>subscriber</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/268</guid>
      <comments>https://icksw.tistory.com/268#entry268comment</comments>
      <pubDate>Sat, 23 Oct 2021 16:28:08 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2021] Meet Group Activities</title>
      <link>https://icksw.tistory.com/267</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;안녕하세요 Pingu입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;오늘은 WWDC 2021의 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10183/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&quot;Meet Group Activities&quot;라는 영상&lt;/a&gt;을 보고 정리한 글을 써보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 영상은 iOS 15에 새롭게 추가된 Group Activities라는 프레임워크에 대해 알아보는 영상이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Group Activities를 간단하게 말하면 다른 애플 생태계 사용자와 FaceTime과 Message 앱을 사용하면서 다양한 콘텐츠를 함께 즐길 수 있도록 만들어주는 프레임워크였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;Meet Group Activities&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플은 함께 뭔갈 즐기고 있는 사람들에게 같은 방에 있는 것처럼 느낄 수 있도록 하는 방법을 고민했고 이를 위한 방법으로 iOS 15에서 추가한 기능이 &quot;SharePlay&quot;라고 합니다. 이번 영상에서는 앱에서 이를 사용하는 방법을 알아본다고 하네요. 일단 사용하기 전에 Share Play의 구성에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Communication&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdnVk8/btrhhR7qAiR/PGzrpOD7RRHmSgKev0Mbo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdnVk8/btrhhR7qAiR/PGzrpOD7RRHmSgKev0Mbo0/img.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;524&quot; style=&quot;width: 44.17853904747605%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdnVk8/btrhhR7qAiR/PGzrpOD7RRHmSgKev0Mbo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdnVk8%2FbtrhhR7qAiR%2FPGzrpOD7RRHmSgKev0Mbo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mDvLO/btrhfy8O2Js/YBfNG9sUzhM6PKk0QCKsp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mDvLO/btrhfy8O2Js/YBfNG9sUzhM6PKk0QCKsp0/img.png&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;368&quot; style=&quot;width: 54.65867025484954%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mDvLO/btrhfy8O2Js/YBfNG9sUzhM6PKk0QCKsp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmDvLO%2Fbtrhfy8O2Js%2FYBfNG9sUzhM6PKk0QCKsp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;782&quot; height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 1년간 코로나로 인해 멀리 떨어진 상태에서의 커뮤니케이션이 중요해졌다고 생각한 애플은 FaceTime, Message에 Share Play 기능을 추가했다고 합니다. FaceTime을 하는 친구와 영화를 보는 등의 활동이 가능하게 된 것이죠. 이런 기능이 어떻게 작동하는지 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;450&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R917m/btrhe0EyIN2/ptSjOX38pqPf9Aj4mMDudK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R917m/btrhe0EyIN2/ptSjOX38pqPf9Aj4mMDudK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R917m/btrhe0EyIN2/ptSjOX38pqPf9Aj4mMDudK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR917m%2Fbtrhe0EyIN2%2FptSjOX38pqPf9Aj4mMDudK%2Fimg.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;450&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것은 Session에서 시작됩니다. 어떤 그룹에 속한 인원들이 뭔가를 시작하기 전에 모두 공유할 준비가 되도록 Session에 참가해야 하죠. 사용자가 session에 참가하면, 기존에 가능했던 text, audio, video를 통해 소통이 가능합니다. 그리고 사용자에게 session을 관리할 수 있는 일관된 system-wide를 제공합니다. 사용자는 session에 새로운 사람을 추가하거나, 떠날 수 있고, session에 참가한 사용자는 전체 시스템을 탐색하고 모든 앱에 접근할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 이런 모든 기능을 Group Activies를 활용해서 개발할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Platform&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;526&quot; width=&quot;466&quot; height=&quot;283&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntWWt/btrheAyBQFq/KNK0rUvTyLqGPWCA9uOo61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntWWt/btrheAyBQFq/KNK0rUvTyLqGPWCA9uOo61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntWWt/btrheAyBQFq/KNK0rUvTyLqGPWCA9uOo61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntWWt%2FbtrheAyBQFq%2FKNK0rUvTyLqGPWCA9uOo61%2Fimg.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;526&quot; width=&quot;466&quot; height=&quot;283&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;다음 구성 요소는 Platform 입니다. 애플은 여러 개의 제품에서 Share Play가 쉽고 동일하게 사용할 수 있기를 바랐다고 해요. 그래서 iOS, iPadOS, macOS에서 작동하는 통합 환경을 구축하고 있고, WebKit이 있는 웹사이트에서도 Group Activities를 사용할 수 있다고 합니다. 물론 AppleTV에서도 사용 가능하다고 하네요. 어떤 제품이던 Session에 있다면 인식이 되고 session에 참여할 수 있기 때문에 여러 제품에서도 잘 사용할 수 있다고 합니다.&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;Playback&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Share Play를 사용할 때 에어팟과 같은 블루투스 장치에 고품질의 오디오를 전달하도록 설계했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 API의 표현력을 최대한 활용하여 다양한 유형의 Share Play 활동이 생겨나기를 바라고, 미디어를 공유하는 경험을 쉽게 구현할 수 있도록 많은 개선 사항을 만들었다고 합니다.&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-origin-width=&quot;536&quot; data-origin-height=&quot;508&quot; width=&quot;273&quot; height=&quot;259&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxrNHO/btrhjsfNpV4/EKiLDXKUzK4BMVsPUWR0R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxrNHO/btrhjsfNpV4/EKiLDXKUzK4BMVsPUWR0R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxrNHO/btrhjsfNpV4/EKiLDXKUzK4BMVsPUWR0R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxrNHO%2FbtrhjsfNpV4%2FEKiLDXKUzK4BMVsPUWR0R1%2Fimg.png&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;508&quot; width=&quot;273&quot; height=&quot;259&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미디어를 사용할 때 가장 중요한 것은 재생 버튼인데요, 재생 버튼을 누르고 난 후부터 콘텐츠를 즐길 수 있기 때문입니다. 애플은 시스템의 모든 재생 버튼이 Share Play와 함께 작동할 수 있도록 노력했다고 합니다. 즉 사용자가 FaceTime을 즐기는 도중 모든 미디어를 친구와 공유할 수 있다고 느끼길 바란다는 뜻이 되겠죠. 따라서 이를 위한 새로운 API가 제공된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;522&quot; width=&quot;488&quot; height=&quot;411&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0zL3T/btrhn28JYbJ/zKJPeYP8NH7DCwqzAug66k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0zL3T/btrhn28JYbJ/zKJPeYP8NH7DCwqzAug66k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0zL3T/btrhn28JYbJ/zKJPeYP8NH7DCwqzAug66k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0zL3T%2Fbtrhn28JYbJ%2FzKJPeYP8NH7DCwqzAug66k%2Fimg.png&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;522&quot; width=&quot;488&quot; height=&quot;411&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Share Play를 위한 새로운 API를 사용하면 그룹 대화가 활성화된 상태에서의 재생 버튼은 위와 같이 미디어를 공유할 수 있는 기능이 추가됩니다.&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;Time-Synced Playback&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Time-Synced Playback에 대해 알아보도록 하겠습니다.&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-origin-width=&quot;1188&quot; data-origin-height=&quot;472&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDaiDO/btrhvO9qSR8/PiDTakWcoTV3Dbm38Mb9T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDaiDO/btrhvO9qSR8/PiDTakWcoTV3Dbm38Mb9T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDaiDO/btrhvO9qSR8/PiDTakWcoTV3Dbm38Mb9T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDaiDO%2FbtrhvO9qSR8%2FPiDTakWcoTV3Dbm38Mb9T0%2Fimg.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;472&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Share Play를 통해 여러 사람들과 미디어를 공유했는데, 위와 같이 모두 영상의 서로 다른 시간을 보고 있다면 어떻게 될까요? 갑자기 어떤 사람은 웃고 어떤 사람은 우는 이상한 상황이 발생할 수 있습니다. 즉 재생 시간을 동일하게 맞추는 것이 중요하다고 할 수 있습니다.&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-origin-width=&quot;1134&quot; data-origin-height=&quot;406&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCXK8b/btrhqgTaTc1/DbA0Cgdqeip36kZRCnDNnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCXK8b/btrhqgTaTc1/DbA0Cgdqeip36kZRCnDNnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCXK8b/btrhqgTaTc1/DbA0Cgdqeip36kZRCnDNnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCXK8b%2FbtrhqgTaTc1%2FDbA0Cgdqeip36kZRCnDNnk%2Fimg.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;406&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Share Play를 사용하면 재생 시간을 플랫폼 수준에서 동기화된 상태로 유지하기 때문에 이를 걱정할 필요가 없다고 합니다. 이를 위해 iPhone과 같은 기기의 AVFoundation 스택에 통합된 새로운 playback-sync 프로토콜을 만들었다고 합니다. 이를 통해 그룹의 누군가가 재생을 누르면 그룹의 모든 사람의 영상이 재생되게 됩니다. 누군가 보여주고 싶은 장면으로 이동하면 모두 해당 화면으로 이동하게 되겠죠.&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-origin-width=&quot;972&quot; data-origin-height=&quot;566&quot; width=&quot;772&quot; height=&quot;450&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZXKeA/btrhfxB9PEW/D9A9fu6kNz08kdAZOLb6oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZXKeA/btrhfxB9PEW/D9A9fu6kNz08kdAZOLb6oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZXKeA/btrhfxB9PEW/D9A9fu6kNz08kdAZOLb6oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZXKeA%2FbtrhfxB9PEW%2FD9A9fu6kNz08kdAZOLb6oK%2Fimg.png&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;566&quot; width=&quot;772&quot; height=&quot;450&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;또한 여러 명의 사람과 함께 볼 수 있기 때문에 서로 대화를 하더라고 자연스럽게 느껴지도록 하기 위한 Smart Volume이라는 기능을 사용하면 콘텐츠 재생 중에 말을 하면 콘텐츠의 오디오를 잠깐 줄였다가 대화가 끝나면 다시 복구한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Picture in Pictrue(PIP)와 잘 작동하도록 만들어서 사용자가 멀티태스킹을 하더라도 괜찮다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Content&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;749&quot; height=&quot;379&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;534&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blGnRZ/btrhd6ExxCR/jdkHGdcFdfUtozTcAoRVZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blGnRZ/btrhd6ExxCR/jdkHGdcFdfUtozTcAoRVZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blGnRZ/btrhd6ExxCR/jdkHGdcFdfUtozTcAoRVZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblGnRZ%2Fbtrhd6ExxCR%2FjdkHGdcFdfUtozTcAoRVZ0%2Fimg.png&quot; width=&quot;749&quot; height=&quot;379&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;534&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;애플은 FaceTime으로 통화를 하면서 앱으로 이동하여 콘텐츠를 공유할 수 있기를 기대한다고 합니다. Share Play를 사용하면 제품의 터치포인트를 확장하고 사용자가 앱을 사용하는 시간을 늘릴 수 있을 것으로 기대하는 듯합니다. 새로운 앱 사용자에게 기존 사용자가 앱을 설명해주는 활동도 해줄 수 있을 것으로 기대한다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;What are Group Activities?&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;210&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7Po6d/btrhfztgbG4/O5KvUFOgZKox7crRlNfZk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7Po6d/btrhfztgbG4/O5KvUFOgZKox7crRlNfZk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7Po6d/btrhfztgbG4/O5KvUFOgZKox7crRlNfZk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7Po6d%2FbtrhfztgbG4%2FO5KvUFOgZKox7crRlNfZk1%2Fimg.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;210&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 Share Play의 구성 요소에 대해 간단히 알아봤으니 이젠 프레임워크의 핵심 개념인 Group Activities에 대해 알아보도록 하겠습니다. Group Activities는 Share Play를 사용하여 FaceTime에서 사람들과 공유하고 즐길 수 있도록 해주는 객체입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;456&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxJsLw/btrhqglmhig/3X8wER6r5C8W5gY8vuARg0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxJsLw/btrhqglmhig/3X8wER6r5C8W5gY8vuARg0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxJsLw/btrhqglmhig/3X8wER6r5C8W5gY8vuARg0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bxJsLw/btrhqglmhig/3X8wER6r5C8W5gY8vuARg0/img.gif&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;456&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 위와 같이 앱에서 Group Activity를 시작할 수 있습니다. 통화 중에 사용자는 앱으로 이동하여 앱이 Group Activity를 채택한 경우 앱이 Share Play를 지원한다는 알림을 받게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 활동을 구성하기 위해서는 Group Activity 프로토콜을 채택한 객체를 만들어야 합니다. 이를 만든 뒤에는 prepareForActivation API를 호출하여 활동을 공유하기 시작합니다. 이 API는 사용자에게 활동을 FaceTime을 함께 사용 중인 모든 사람과 공유하거나 로컬에서만 보는 옵션을 제공합니다. 만약 그룹에 공유하기로 결정한다면 Group Activities에서 이를 알린 뒤 Group Session 객체에 참여할 수 있게 됩니다.&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-origin-width=&quot;768&quot; data-origin-height=&quot;456&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pWqxL/btrhn125gEl/bCwRkgI6K7zgRM6bB8EOr1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pWqxL/btrhn125gEl/bCwRkgI6K7zgRM6bB8EOr1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pWqxL/btrhn125gEl/bCwRkgI6K7zgRM6bB8EOr1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/pWqxL/btrhn125gEl/bCwRkgI6K7zgRM6bB8EOr1/img.gif&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;456&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사용자들이 Group Session에 참여하게 되면, 재생, 일시 정지, 특정 시점으로 이동할 때 그룹원과 비디오가 동기화됩니다. 이러한 이벤트는 발생할 때마다 Group Activities는 사용자에게 자동적으로 알려서 처리한다고 합니다.&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-origin-width=&quot;410&quot; data-origin-height=&quot;716&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciPzys/btrhtVHMjou/JAwSPQBHAJLuFOe5cSxT1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciPzys/btrhtVHMjou/JAwSPQBHAJLuFOe5cSxT1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciPzys/btrhtVHMjou/JAwSPQBHAJLuFOe5cSxT1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciPzys%2FbtrhtVHMjou%2FJAwSPQBHAJLuFOe5cSxT1k%2Fimg.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;716&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 위와 같이 사용자가 자신 혹은 전체 그룹에 대한 activities를 종료하도록 선택할 수 있다고 합니다.&lt;/p&gt;
&lt;h1&gt;Concepts and architecture&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;160&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCLjP1/btrhd56KM7k/K9WvPZGbD2rtKZZhajlmkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCLjP1/btrhd56KM7k/K9WvPZGbD2rtKZZhajlmkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCLjP1/btrhd56KM7k/K9WvPZGbD2rtKZZhajlmkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCLjP1%2Fbtrhd56KM7k%2FK9WvPZGbD2rtKZZhajlmkK%2Fimg.png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;160&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 Group Activities라는 새로운 프레임워크의 몇 가지 고급 개념과 아키텍처에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;694&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsh5wC/btrhgk3DOlc/l4uk8d2cF9t8FJsxALDgPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsh5wC/btrhgk3DOlc/l4uk8d2cF9t8FJsxALDgPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsh5wC/btrhgk3DOlc/l4uk8d2cF9t8FJsxALDgPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsh5wC%2Fbtrhgk3DOlc%2Fl4uk8d2cF9t8FJsxALDgPK%2Fimg.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;694&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group Activities는 FaceTime를 사용 중에 사용자들끼리 공유 경험을 제공할 수 있는 새로운 Swift 프레임워크입니다. 이는 AVFoundation과 통합되어 공유 비디오, 오디오 재생 경험을 쉽게 구현할 수 있도록 만들어졌다고 해요.&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-origin-width=&quot;606&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vUvTo/btrhtUPDsh6/EkszDK1tmE2dkN92xIrTCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vUvTo/btrhtUPDsh6/EkszDK1tmE2dkN92xIrTCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vUvTo/btrhtUPDsh6/EkszDK1tmE2dkN92xIrTCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvUvTo%2FbtrhtUPDsh6%2FEkszDK1tmE2dkN92xIrTCk%2Fimg.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group Activity API의 high-level 개념을 살펴보기 전에 두 가지 핵심 부분인 GroupActivity, GroupSession에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GroupActivity&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;344&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csOxaG/btrhjs1curZ/sDbuYp5UquNhiaKdibmErk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csOxaG/btrhjs1curZ/sDbuYp5UquNhiaKdibmErk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csOxaG/btrhjs1curZ/sDbuYp5UquNhiaKdibmErk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsOxaG%2Fbtrhjs1curZ%2FsDbuYp5UquNhiaKdibmErk%2Fimg.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;344&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GroupActivity는 앱이 공유 경험을 정의할 때 사용합니다. 이는 앱이 공유 기능을 제공하기 위해 필요한 정보들을 가지고 있습니다. 예를 들어 공유 오디오, 비디오 재생을 사용하는 경우 재생 중인 콘텐츠의 URL을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 앱에서 직접 구현한 공유 기능을 제공할 수도 있습니다. 예를 들면 함께 그리는 기능을 제공한다면 사용자가 그리는 내용에 대한 정보가 저장될 거예요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GroupSession&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;476&quot; width=&quot;551&quot; height=&quot;375&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO1GwQ/btrhlzltZ4Q/63vJ7uHFtDtCOB21mi4oQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO1GwQ/btrhlzltZ4Q/63vJ7uHFtDtCOB21mi4oQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO1GwQ/btrhlzltZ4Q/63vJ7uHFtDtCOB21mi4oQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO1GwQ%2FbtrhlzltZ4Q%2F63vJ7uHFtDtCOB21mi4oQK%2Fimg.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;476&quot; width=&quot;551&quot; height=&quot;375&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GroupSession은 공유 기능에 참여하는 그룹을 나타냅니다. 그룹의 참가자와 같은 값들에 대한 접근을 제공하며 프레임워크에는 GroupSession과 함께 사용해서 장치 간 데이터를 주고받을 수 있는 API도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GroupSession은 많은 데이터를 교환하기 위한 것은 아니라고 합니다. GroupSession을 통해 노래를 함께 듣는 경우 노래를 직접적으로 주고받지는 않는다고 합니다. GroupSession은 그런 작업이 아닌 AVFoundation에서 재생, 일시정지, 탐색과 같은 이벤트를 주고받아서 그룹원의 콘텐츠를 동기화 상태로 유지하기 위해 사용한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GroupSession에서 제공하는 통신 매체는 end-to-end 암호화가 되기 때문에 기기별 앱 외에는 이 채널을 통해 교환되는 데이터를 볼 수도 없다고 합니다.&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;Initiating an activity&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 동작되는 상황을 세부적으로 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;656&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tPjQi/btrhn3NqkUB/1aXzM6lAkj6q3PE58Pz500/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tPjQi/btrhn3NqkUB/1aXzM6lAkj6q3PE58Pz500/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tPjQi/btrhn3NqkUB/1aXzM6lAkj6q3PE58Pz500/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtPjQi%2Fbtrhn3NqkUB%2F1aXzM6lAkj6q3PE58Pz500%2Fimg.png&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;656&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 두 개의 아이폰이 있고 왼쪽은 Bhaskar, 오른쪽은 Pierre라는 사람의 폰입니다. 둘은 FaceTime 통화를 하고 있고 &quot;Awesome App&quot;이라는 Group Activity를 사용하여 공유 기능을 제공하는 앱을 사용하고 있다고 합니다. 왼쪽 폰에서 activity를 시작한다고 가정해볼게요.&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-origin-width=&quot;1244&quot; data-origin-height=&quot;660&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u1np9/btrhh7C12WR/3WPaEJxa1pwKeeFUUSxfzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u1np9/btrhh7C12WR/3WPaEJxa1pwKeeFUUSxfzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u1np9/btrhh7C12WR/3WPaEJxa1pwKeeFUUSxfzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu1np9%2Fbtrhh7C12WR%2F3WPaEJxa1pwKeeFUUSxfzk%2Fimg.png&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;660&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 가장 먼저 해야 할 일은 Group Activity 프로토콜을 준수하는 객체를 만드는 일입니다. 위 그림에서는 AwesomeActivity라는 이름의 객체가 생성되었네요. 이 객체는 앞에서 알아봤듯이 공유 활동에 대한 정보가 포함되어있습니다. 재생할 콘텐츠에 대한 정보나 함께 그리는 기능을 제공하는 앱의 경우 무엇을 그릴지에 대한 정보가 있을 수 있겠네요.&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-origin-width=&quot;1262&quot; data-origin-height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXsaV/btrhezmcBpx/CFP26IgDVHOxfdLmvvK740/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXsaV/btrhezmcBpx/CFP26IgDVHOxfdLmvvK740/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXsaV/btrhezmcBpx/CFP26IgDVHOxfdLmvvK740/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXsaV%2FbtrhezmcBpx%2FCFP26IgDVHOxfdLmvvK740%2Fimg.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 activity를 생성한 뒤 할 일은 prepareForActivation을 호출하는 작업입니다. 이를 호출하면 사용자가 activity를 시작할지 물어보는 메시지가 표시되고 사용자 동의를 얻어야 activity를 시작할 수 있다고 합니다. 애플 제품에서 사용자 권한을 얻는 여러 작업들처럼 위 단계는 필수라고 하네요.&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-origin-width=&quot;1176&quot; data-origin-height=&quot;642&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnslWM/btrhhSSQ9jR/kXvlfnFoWcNxL9MjUgaXn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnslWM/btrhhSSQ9jR/kXvlfnFoWcNxL9MjUgaXn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnslWM/btrhhSSQ9jR/kXvlfnFoWcNxL9MjUgaXn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnslWM%2FbtrhhSSQ9jR%2FkXvlfnFoWcNxL9MjUgaXn1%2Fimg.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;642&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사용자가 권한을 앱에게 줬다면 앱은 activity 객체의 activate를 호출하고 앱이 공유 기능을 시작할 것이라는 것을 시스템에 알린다고 합니다.&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;Observing a session&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으로 Observing session에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;646&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QdcYW/btrhtUITzrY/nDZeOZejz5lnBXMh3yrsRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QdcYW/btrhtUITzrY/nDZeOZejz5lnBXMh3yrsRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QdcYW/btrhtUITzrY/nDZeOZejz5lnBXMh3yrsRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQdcYW%2FbtrhtUITzrY%2FnDZeOZejz5lnBXMh3yrsRk%2Fimg.png&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;646&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 상태는 방금 전 예에서, 마지막에 언급됐던 activate를 호출한 상태입니다. 이 상태에서 앱은 GroupSession 클래스의 AsyncSequence를 통해 incoming session을 반복해야 합니다.&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-origin-width=&quot;1226&quot; data-origin-height=&quot;656&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7H7oJ/btrhn2uaB0f/SPt01CvEmTWtFVOf9U1bfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7H7oJ/btrhn2uaB0f/SPt01CvEmTWtFVOf9U1bfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7H7oJ/btrhn2uaB0f/SPt01CvEmTWtFVOf9U1bfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7H7oJ%2Fbtrhn2uaB0f%2FSPt01CvEmTWtFVOf9U1bfk%2Fimg.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;656&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 다음 session이 있는 경우 공유 경험에 대한 GroupSession 객체가 앱에 전달됩니다. 이는 앱을 실행하는 기기나 session을 수신하는 원격 기기에서든 앱이 취하는 동일한 단계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group Session의 시작 및 observing에 대한 자세한 내용은 아래 영상을 보라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Coordinate media playback in Safari with Group Activities&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Joining a session&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 하면 앱이 session에 전달되었기 때문에 session에 참여하기 전에 앱의 설정이 필요합니다.&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-origin-width=&quot;1264&quot; data-origin-height=&quot;610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgUNdY/btrhgkbC6kF/tqiaubKwr7qoYoHioZzoxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgUNdY/btrhgkbC6kF/tqiaubKwr7qoYoHioZzoxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgUNdY/btrhgkbC6kF/tqiaubKwr7qoYoHioZzoxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgUNdY%2FbtrhgkbC6kF%2FtqiaubKwr7qoYoHioZzoxK%2Fimg.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session에 참여하기 전에 앱을 설정하는 것은 앱의 기능에 따라 다른 설정일 수 있는데요, 예를 들어 함께 그리는 앱과 동영상 공유 앱은 설정할 것들이 다를 거예요. 위 그림은 공유 미디어 기능을 제공하는 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱은 AVPlayer의 AVPlaybackCoordinator를 GroupSession과 연결하여 연결된 AVPlayer가 프레임워크에서 제공하는 통신 채널을 통해 콘텐츠를 동기화할 수 있도록 하는데요, 이 동기화는 AVPlayer에만 사용할 수 있는 것이 아닌 직접 만든 비디오 플레이어에도 사용할 수 있으며 AVDelegatingPlaybackCoordinator를 통해 동기화 지원을 계속해서 받을 수 있다고 합니다.&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-origin-width=&quot;1232&quot; data-origin-height=&quot;646&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba4v0c/btrhjW8wFvy/YWE6gT5Aa2q7RrEb8BApnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba4v0c/btrhjW8wFvy/YWE6gT5Aa2q7RrEb8BApnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba4v0c/btrhjW8wFvy/YWE6gT5Aa2q7RrEb8BApnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba4v0c%2FbtrhjW8wFvy%2FYWE6gT5Aa2q7RrEb8BApnK%2Fimg.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;646&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 앱 설정을 완료하면 앱이 GroupSession에서 join()을 호출합니다. join()이 호출되면 시스템은 서로 다른 기기에서 실행되는 앱 간 end-to-end 암호화된 채널을 설정합니다. 이 시점에서 앱은 데이터를 동기화하고 사용자가 공유 환경에 참여할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 공유 환경을 생성하는 경우 앱은 이 채널을 사용하여 데이터를 교환하여 사용자들을 동일한 상태로 유지할 수 있다고 합니다. 또한 미디어를 공유하는 앱에서 사용자가 재생, 일시 정지와 같은 이벤트를 발생시킬 때 AVFoundation에서 이 채널을 사용하여 동기화 상태를 유지한다고 합니다. 앞에서도 한 번 언급됐었는데, 이 채널은 많은 데이터를 교환하는 데 사용되지는 않고 사용자들의 콘텐츠들을 동일한 상태로 유지하기 위한 정보만 교환하는데 사용하라고 하네요.&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;Posting events&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;622&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0Tio6/btrhjtTpROD/5Sm7hwHplLudPeirAaQB91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0Tio6/btrhjtTpROD/5Sm7hwHplLudPeirAaQB91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0Tio6/btrhjtTpROD/5Sm7hwHplLudPeirAaQB91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0Tio6%2FbtrhjtTpROD%2F5Sm7hwHplLudPeirAaQB91%2Fimg.png&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;622&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 앱에 session이 있고 사용자가 공유 기능을 즐길 수 있다고 합니다. 여기서 좀 더 많은 기능을 제공하기 위해 할 수 있는 일은 프레임워크를 사용하여 이벤트를 게시하는 것이라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트를 통해 사용자는 공유 환경에서 무슨 일이 일어나는지 알 수 있다고 합니다. 예를 들어 이벤트를 게시하여 누군가가 트랙을 재생, 일시정지할 때 사용자에게 알릴 수 있다고 합니다. 이벤트를 게시하면 시스템에서 사용자에게 알리는 알림을 표시할 수 있는데, 현재 API에서는 미디어 재생에 대한 이벤트에 대해서만 해당 기능을 제공한다고 합니다. 따라서 AVPlayer, AVDelegatingPlaybackCoordinator를 사용하면 해당 기능을 쉽게 사용할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이를 사용하지 않더라도 프레임워크를 사용해서 이벤트를 게시할 수도 있다고 합니다. 이를 위해 이벤트를 게시하는 자세한 방법은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10187&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Build custom experiences with Group Activities&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;482&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFlmUF/btrhsgepIp5/2OFiq4tZRYusa8SppoTGpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFlmUF/btrhsgepIp5/2OFiq4tZRYusa8SppoTGpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFlmUF/btrhsgepIp5/2OFiq4tZRYusa8SppoTGpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFlmUF%2FbtrhsgepIp5%2F2OFiq4tZRYusa8SppoTGpk%2Fimg.png&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;482&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상을 마무리하며 이번 영상에서 본 내용을 정리하면 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Group Activities는 FaceTime을 통해 공유 기능을 생성할 수 있는 새로운 프레임워크입니다.&lt;/li&gt;
&lt;li&gt;프레임워크는 iOS, iPadOS, macOS, tvOS 모두 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;앱이 미디어를 공유하기 위해서 AVFoundation와 통합되었습니다.&lt;/li&gt;
&lt;li&gt;macOS에서 웹을 통한 재생 동기화도 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이번 영상은 마무리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 iOS 15의 새로운 기능 중 가장 기대되는 기능 중 하나로 빨리 나왔으면 좋겠는데.. 아직은 FaceTime에서 사용할 수 없더라고요.&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;그럼 오늘 WWDC 영상인 &quot;Meet&amp;nbsp;Group&amp;nbsp;Activities&quot;는 여기까지 정리하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다. &lt;/p&gt;</description>
      <category>Apple/WWDC 2021</category>
      <category>2021</category>
      <category>GroupActivities</category>
      <category>IOS</category>
      <category>iOS15</category>
      <category>ipados</category>
      <category>MacOS</category>
      <category>SharePlay</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/267</guid>
      <comments>https://icksw.tistory.com/267#entry267comment</comments>
      <pubDate>Tue, 12 Oct 2021 02:16:38 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2021] Meet async / await in Swift</title>
      <link>https://icksw.tistory.com/266</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 WWDC 2021의 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10132/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&quot;Meet async / await in Swift&quot;&lt;/a&gt;라는 영상을 정리한 글을 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상을 한 문장으로 요약하자면 &quot;Swift 5.5에서 추가된 async / await 상세 사용법 및 동작원리&quot; 정도? 였던 거 같습니다.&lt;/p&gt;
&lt;h1&gt;Meet async / await in Swift&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에 추가된 async / await로 이제 쉽고 안전하게 비동기 코드를 작성할 수 있다고 합니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oN9kV/btrgbmN1upD/bnvjY2smLt29MgqLVfFLUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oN9kV/btrgbmN1upD/bnvjY2smLt29MgqLVfFLUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oN9kV/btrgbmN1upD/bnvjY2smLt29MgqLVfFLUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoN9kV%2FbtrgbmN1upD%2FbnvjY2smLt29MgqLVfFLUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;202&quot; data-origin-width=&quot;718&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;지금까지 저도 위와 같이 completionHandler나 delegate 패턴을 활용해서 비동기 처리를 했었는데요, 이렇게 하면 비동기로 프로그램을 작성할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ApTAn/btrga3BdtWh/5a7UBFbeIsuCWEMk7oRHH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ApTAn/btrga3BdtWh/5a7UBFbeIsuCWEMk7oRHH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ApTAn/btrga3BdtWh/5a7UBFbeIsuCWEMk7oRHH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FApTAn%2Fbtrga3BdtWh%2F5a7UBFbeIsuCWEMk7oRHH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;784&quot; height=&quot;310&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림을 보면, 첫 번째 스레드는 동기 코드이고, 두 번째 스레드가 비동기 코드입니다. 비동기 코드로 프로그램을 작성하면, 어느 정도 시간이 걸리는 작업을 수행할 때 해당 작업이 완료되기를 기다리며 스레드를 차단하지 않고, 스레드가 다른 일을 하도록 합니다. 그러다가 수행하던 작업이 완료되면 completionHandler를 호출하여 다음 작업을 진행하게 되죠.&lt;/p&gt;
&lt;p 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;602&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E7QQd/btrfXHF3c2p/cC1gKqHvLRk3s7FQd8KDuK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E7QQd/btrfXHF3c2p/cC1gKqHvLRk3s7FQd8KDuK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E7QQd/btrfXHF3c2p/cC1gKqHvLRk3s7FQd8KDuK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/E7QQd/btrfXHF3c2p/cC1gKqHvLRk3s7FQd8KDuK/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;520&quot; height=&quot;292&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 앱에서 여러개의 이미지를 서버에서 받아올 때 이미지들이 순차적으로 로딩되는 것을 본 경험은 다들 해보셨을 거예요. 이러한 작업이 비동기 작업인데요, 해당 작업이 처리되는 과정은 아래와 같습니다.&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;553&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyMMq/btrf7BrGIVm/RPMH2zoKXXTu43Atv8R2k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyMMq/btrf7BrGIVm/RPMH2zoKXXTu43Atv8R2k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyMMq/btrf7BrGIVm/RPMH2zoKXXTu43Atv8R2k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkyMMq%2Fbtrf7BrGIVm%2FRPMH2zoKXXTu43Atv8R2k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;329&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;URL을 통해 썸네일 이미지를 요청하기 위한 URLRequest를 생성합니다.&lt;/li&gt;
&lt;li&gt;URLSession의 dataTask 메서드에서 URLRequest에 대한 데이터를 가지고 옵니다.&lt;/li&gt;
&lt;li&gt;가지고 온 데이터를 UIImage의 init(data:)를 통해 이미지로 만듭니다.&lt;/li&gt;
&lt;li&gt;만들어진 이미지를 prepareThumbnail 메서드로 썸네일을 만들어서 화면에 보여줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 과정이 순차적으로 진행되어야 원하는 작업이 수행될 수 있어요. 그런데 여기서&amp;nbsp;1, 3번 작업은 빠르게 처리되겠지만 2,4번 작업은 시간이 좀 걸릴 수 있습니다. 특히 2번 작업의 경우엔 이미지의 크기가 크다면 상당히 오래 걸릴 수도 있죠. 또한 아까의 예와 같이 여러 개의 이미지 데이터를 다운로드하려면 시간이 더 필요할 수도 있습니다. 따라서 이미지를 다운로드하는 동안 다른 작업을 수행하고 싶고, 이를 위해서는 비동기 코드를 작성해야 합니다! 이를 위해 지금까지 작성했던 코드는 아래와 같습니다.&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;536&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b44IRG/btrgbnlUrSU/E2319ZLj3hyzdjXnhra9xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b44IRG/btrgbnlUrSU/E2319ZLj3hyzdjXnhra9xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b44IRG/btrgbnlUrSU/E2319ZLj3hyzdjXnhra9xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb44IRG%2FbtrgbnlUrSU%2FE2319ZLj3hyzdjXnhra9xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;280&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;280&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;어쨌든 코드를 보면 dataTask 메서드에서 여러개의 예외들을 조건문으로 분기하고 해당 예외에 맞는 에러로 completion 메서드를 호출하는 것을 볼 수 있습니다. 원하는 대로 데이터가 잘 넘어와도 UIImage 생성자에서 오류가 발생하면 guard let 구문으로 처리해주고 prepareThumbnail 메서드에서도 thumbnail이 잘 만들어졌는지 guard let으로 처리해주는 것도 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 위의 코드를 보면 guard let 구문에서는 그냥 return만 작성되어있고 completion 메서드를 호출하지 않는 것을 볼 수 있습니다. 이건 개발자가 실수를 한 건데요, 그래서 수정한 코드는 아래와 같습니다.&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;1262&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds2E90/btrgdF7aZ65/OgVLSbIARmE0ZMAuZKVIi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds2E90/btrgdF7aZ65/OgVLSbIARmE0ZMAuZKVIi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds2E90/btrgdF7aZ65/OgVLSbIARmE0ZMAuZKVIi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds2E90%2FbtrgdF7aZ65%2FOgVLSbIARmE0ZMAuZKVIi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;736&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 모든 예외상황에 completion을 호출해줘야 어떤 상황이 발생하더라도 원하는대로 작업이 수행될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 함수는 throw 키워드를 사용해서 에러를 던질 수 있는데, 이러한 completion 메서드를 사용하는 함수에서는 오류를 throw 할 수 없어서 Swift가 오류를 인지할 수가 없습니다. 즉 사용자가 사용하다가 우연히 오류를 발견해야 합니다. 이건 이미 사용자에겐 오류가 발생했으니.. 문제가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위와 같이 에러를 포함한 정상 처리까지 총 5개의 completion 메서드 호출을 작성해야 원하는 작업을 만들 수 있어서 코드가 지저분합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baI5Dz/btrf5jdMBtw/B4vyFzZYtiWsNVKvOphtLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baI5Dz/btrf5jdMBtw/B4vyFzZYtiWsNVKvOphtLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baI5Dz/btrf5jdMBtw/B4vyFzZYtiWsNVKvOphtLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaI5Dz%2Fbtrf5jdMBtw%2FB4vyFzZYtiWsNVKvOphtLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;176&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 작업을 async / await로 작성하면!! 코드가 엄청나게 간단해집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1632756307954&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for id: String) async throws -&amp;gt; UIImage {
    let request = thumbnailURLRequest(for: id)
    let (data, response) = try await URLSession.shared.data(for: request)
    guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
    let maybeImage = UIImage(data: data)
    guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
    return thumbnail
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 선언부를 보면 async 키워드가 있는것을 볼 수 있습니다. 그리고 에러 처리를 위한 throws 키워드도 보입니다. 즉 아까 방식과는 다르게 async / await를 사용하면 에러 처리도 간단하게 할 수 있다는 말이 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thumbnailURLRequest 메서드는 동기 코드니까 스레드를 차단한 상태로 진행되고, 그 다음 줄부터 비동기로 수행됩니다. data(for:) 메서드로 데이터와 HTTP 응답을 받아오는데, 이 코드 앞에 await라는 키워드가 보일 거예요. 이 코드가 있으면 해당 작업을 수행하는 동안 스레드를 점유 해제합니다. 그러면 다른 작업들이 수행될 수 있게 됩니다. 이렇게 async 함수에서 특정 작업이 수행되는 것을 기다리고 싶다면 await 키워드를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 뭐 어디서든 에러가 발생하면 아까와 다르게 그냥 던져버리면 됩니다. 그러다가 생성된 이미지의 썸네일 이미지를 만드는 곳에서 다시 await 키워드를 사용해서 해당 작업이 완료되는것을 기다리며 스레드를 반환합니다. 물론 여기서도 오류가 발생하면 에러를 던집니다.&lt;/p&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 / await를 사용하니까 아까 20줄 가까이 되던 코드가 6줄로 줄어든 것을 볼 수 있습니다! 또한 코드가 수행순서대로 작성되어 있으며 에러 처리도 쉽게 할 수 있고 아까와 같이 에러를 빼먹는 실수도 방지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 좀 더 자세히 보면, 마지막 썸네일을 만드는 코드는 함수 호출이 아니었는데도 await를 사용할 수 있었습니다. 즉 함수가 아니더라도 비동기로 만들 수 있답니다! 해당 코드를 보면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H5443/btrfXG1nCM9/2zmNtRtKzruN0WDXKOINKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H5443/btrfXG1nCM9/2zmNtRtKzruN0WDXKOINKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H5443/btrfXG1nCM9/2zmNtRtKzruN0WDXKOINKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH5443%2FbtrfXG1nCM9%2F2zmNtRtKzruN0WDXKOINKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1244&quot; height=&quot;526&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 UIImage의 read only 프로퍼티로 thumbnail이 있고 해당 프로퍼티의 getter에는 async 키워드가 붙어있습니다. 이렇게 정의되어 있기 때문에 아까와 같이 await를 사용할 수 있었던 거죠. 또한 Swift 5.5부터는 getter에도 throw를 사용해서 에러 처리를 할 수 있다고 합니다. 그리고 중요한 부분인데 read only 프로퍼티에만 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;이렇게 함수, 프로퍼티, 생성자에서 스레드 사용을 일시 중단할 위치를 나타내기 위해 await를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GGdsi/btrgcmtdmsf/CBKzX1kLowFBxmEoLgBPCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GGdsi/btrgcmtdmsf/CBKzX1kLowFBxmEoLgBPCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GGdsi/btrgcmtdmsf/CBKzX1kLowFBxmEoLgBPCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGGdsi%2Fbtrgcmtdmsf%2FCBKzX1kLowFBxmEoLgBPCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;258&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 await를 사용할 수 있는 부분이 있는데 for in 루프에서 async sequence를 사용할 수 있습니다. &amp;nbsp;Async sequence는 item을 비동기적으로 제공한다는 점을 제외하고는 일반적인 sequence와 동일하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 여러개의 값들에 대해 비동기 작업을 진행할 수 있습니다. 그리고 아직은 for 루프에서만 await를 사용할 수 있다고 합니다.&lt;/p&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 Sequence에 대한 좀 더 자세한 내용은 아래 영상들을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://icksw.tistory.com/293&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Meet AsyncSequence&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10134&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Explore structured concurrency in Swift&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이렇게 async 함수를 실행하다가 await 키워드를 만나면 어떤 일이 발생하는지 살펴보도록 하겠습니다. 먼저 일반적인 함수의 경우입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zsC3U/btrf3t10vgK/y5NoxIEqHfc0JfgZFx9eEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zsC3U/btrf3t10vgK/y5NoxIEqHfc0JfgZFx9eEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zsC3U/btrf3t10vgK/y5NoxIEqHfc0JfgZFx9eEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzsC3U%2Fbtrf3t10vgK%2Fy5NoxIEqHfc0JfgZFx9eEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;391&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;654&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYHBvj/btrf3xW8dwB/llKh7nnPQJzgf6etOg57Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYHBvj/btrf3xW8dwB/llKh7nnPQJzgf6etOg57Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYHBvj/btrf3xW8dwB/llKh7nnPQJzgf6etOg57Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYHBvj%2Fbtrf3xW8dwB%2FllKh7nnPQJzgf6etOg57Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1362&quot; height=&quot;602&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 함수의 경우 일반적인 함수와 다릅니다. 일반 함수는 함수가 종료되는 경우에만 스레드를 반환할 수 있었지만 비동기 함수의 경우 suspending(일시 정지)를 통해 스레드를 반환할 수 있습니다. 비동기 함수도 호출될 때는 스레드에 대한 제어권을 받게 됩니다. 그러다 await 키워드를 만나면 스레드를 반환합니다. 이때 호출자에게 스레드를 반환하는 것이 아닌 시스템에 반환합니다. 따라서 시스템은 해당 스레드를 활용하여 다른 작업들을 수행하다가 어느 시점이 되면 시스템은 다시 비동기 함수에 스레드 제어권을 줍니다. 그러다 결국 비동기 함수가 실행을 마치고 에러나 결과를 반환하고, 스레드 권한을 호출자에게 다시 넘기게 됩니다.&lt;/p&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 함수라고 해서 반드시 일시정지해야하는 것도 아니고 await를 만난다고 해서 반드시 일시정지해야 하는 것도 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632759508863&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for id: String) async throws -&amp;gt; UIImage {
    let request = thumbnailURLRequest(for: id)
    let (data, response) = try await URLSession.shared.data(for: request)
    guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
    let maybeImage = UIImage(data: data)
    guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
    return thumbnail
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 일시정지 됐들 때 어떤 일이 발생할 수 있는지 보기 위해 아까 코드를 다시 보겠습니다. URLSession.shared.data 메서드를 호출하면 스레드를 일시 정지할 수 있습니다. 아까 언급한 대로 이 경우 스레드를 시스템에 반환하고, 시스템은 URLSession.shared.data 메서드에 대한 작업을 예약합니다. 이 경우 해당 작업이 바로 시작될 수도 있고 다른 작업이 실행될 수도 있죠. 운영체제 시간에 배운 스케줄링 기법처럼 작업이 스케줄링되는 느낌입니다.&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;761&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGSBJ5/btrga14wOQL/lfLziCq8kKUZsK3sfatsD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGSBJ5/btrga14wOQL/lfLziCq8kKUZsK3sfatsD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGSBJ5/btrga14wOQL/lfLziCq8kKUZsK3sfatsD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGSBJ5%2Fbtrga14wOQL%2FlfLziCq8kKUZsK3sfatsD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;86&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 작업이 예약되어 있는 상태에서 위와 같이 버튼을 탭해서 likeCurrentPost()라는 메서드를 호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chsDvA/btrga2WAuiG/8aAvpYf6GCVWqlPcNfxGw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chsDvA/btrga2WAuiG/8aAvpYf6GCVWqlPcNfxGw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chsDvA/btrga2WAuiG/8aAvpYf6GCVWqlPcNfxGw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchsDvA%2Fbtrga2WAuiG%2F8aAvpYf6GCVWqlPcNfxGw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;62&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;52&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;이렇게 일시정지된 상태에서 다른 작업들이 수행될 수 있기 때문에 앱 상태가 일시정지 중에 변할 수 있음을 유의해야 합니다. 즉 async / await 블록은 하나의 트랜잭션으로 실행되지 않는다는 것을 알 수 있습니다. 또한 반환한 스레드가 아닌 다른 스레드에서 실행될 수도 있다고 합니다. 이는 공유자원이 보호될 수 없을 수도 있음을 의미하며 이를 해결하기 위해서 Actor라는 것이 있는데, 이에 대한 자세한 정보는 아래 영상에서 확인하라고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10133&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Protect mutable state with Swift actors&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdHLU1/btrgbJoKEIX/8zHjdee1YGtkFTv7X5G4H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdHLU1/btrgbJoKEIX/8zHjdee1YGtkFTv7X5G4H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdHLU1/btrgbJoKEIX/8zHjdee1YGtkFTv7X5G4H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdHLU1%2FbtrgbJoKEIX%2F8zHjdee1YGtkFTv7X5G4H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;454&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async / await에 대해 기억해야할 몇 가지를 정리해보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 async로 작성하면 일시정지할 수 있습니다. 이 경우 자신을 호출한 호출자도 일시 중지됩니다. 따라서 호출자도 async로 정의되어 있어야 합니다.&lt;/li&gt;
&lt;li&gt;비동기 함수에서 일시정지될 수 있는 위치를 알리기 위해 await 키워드를 사용합니다.&lt;/li&gt;
&lt;li&gt;비동기 함수가 일시정지되는 동안 스레드는 시스템에 반환되어 다른 작업을 수행할 수 있으므로 앱 상태가 원하지 않게 변할 수 있습니다.&lt;/li&gt;
&lt;li&gt;비동기 함수가 다시 시작되면 호출한 비동기 함수의 반환 결과가 호출자로 전달되고 중단된 곳, 즉 await 키워드 다음 부분부터 다시 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Migration with async / await&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Testing&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기부터 다른 사람이 영상에 등장하면서 &amp;nbsp;async / await가 생겨나면서 이전에 만들어뒀던 코드들을 수정해야 하는 일도 해줄 필요가 있는데, 이런 경우들을 보여준다고 합니다. 그중 Testing 코드부터 언급하네요.&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;1250&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brCN4q/btrgjzMD506/J4Ifq1F95h4NW5g1JHwQY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brCN4q/btrgjzMD506/J4Ifq1F95h4NW5g1JHwQY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brCN4q/btrgjzMD506/J4Ifq1F95h4NW5g1JHwQY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrCN4q%2FbtrgjzMD506%2FJ4Ifq1F95h4NW5g1JHwQY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1250&quot; height=&quot;664&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서는 테스트 코드를 위해 XCTest 프레임워크를 제공합니다. 위의 코드는 XCTest를 활용해서 만든 async / await를 적용하기전 테스트입니다. XCTest에서는 wait라는 함수로 특정 작업들이 성공적으로 수행되는지 확인하는 코드들이 있었는데, 이게 임의로 시간을 설정해야 하다 보니 공부할 때 느낀 바로는 크게 효율적으로 보이진 않았습니다.&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;1204&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jsd6w/btrgjEHcY4C/YNDfS2hPot8KkgbyGbrApk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jsd6w/btrgjEHcY4C/YNDfS2hPot8KkgbyGbrApk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jsd6w/btrgjEHcY4C/YNDfS2hPot8KkgbyGbrApk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJsd6w%2FbtrgjEHcY4C%2FYNDfS2hPot8KkgbyGbrApk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1204&quot; height=&quot;646&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이제 async / await를 적용해서 테스트를 작성하면 아까의 코드보다는 훨씬 짧고 직관적으로 코드를 수정할 수 있습니다. 시간을 임의로 결정하는 것이 아닌, 그냥 작업이 끝나면 Assertion을 처리하는 방식으로 바뀌었네요.&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;Application (SwiftUI)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 앱 코드에 async / await를 적용해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLZH37/btrgjFzmtsx/XzaolMdHqk488yTF8pW5q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLZH37/btrgjFzmtsx/XzaolMdHqk488yTF8pW5q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLZH37/btrgjFzmtsx/XzaolMdHqk488yTF8pW5q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLZH37%2FbtrgjFzmtsx%2FXzaolMdHqk488yTF8pW5q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1222&quot; height=&quot;672&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 SwiftUI 코드로 뷰가 비동기적으로 id를 가지고 썸네일을 가지고 오는 코드입니다. 이를 async / await로 수정하면 아래와 같아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6GyZv/btrgjGdXMbT/cJxikcyFZqRobwpZvzJ470/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6GyZv/btrgjGdXMbT/cJxikcyFZqRobwpZvzJ470/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6GyZv/btrgjGdXMbT/cJxikcyFZqRobwpZvzJ470/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6GyZv%2FbtrgjGdXMbT%2FcJxikcyFZqRobwpZvzJ470%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1214&quot; height=&quot;644&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바뀐 부분은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;completion Handler를 제거하고 try로 오류 처리를 가능하도록 만들고, 비동기 처리를 위해 await 키워드를 써줍니다.&lt;/li&gt;
&lt;li&gt;Swift 컴파일러는 async 컨텍스트가 아닌 곳에서는 비동기 함수를 호출할 수 없기 때문에 Task로 해당 부분을 감싸줍니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉 async 컨텍스트가 아닌 곳에서도 비동기 함수를 호출할 수 있다는 것을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서의 비동기 코드를 잘 활용하는 상세한 방법은 아래 영상을 확인하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10134&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Explore structured concurrency in Swift&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10019&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Discover concurrency in SwiftUI&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 테스트와 앱 코드까지 새로 나온 async / await로 변경이 가능하게 됐습니다. &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;Async API in the SDK&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lubO8/btrgh9HW4bA/rmDmlBH7dzLekZk2mMNtj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lubO8/btrgh9HW4bA/rmDmlBH7dzLekZk2mMNtj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lubO8/btrgh9HW4bA/rmDmlBH7dzLekZk2mMNtj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlubO8%2Fbtrgh9HW4bA%2FrmDmlBH7dzLekZk2mMNtj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;329&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 수많은 SDK들이 기존에는 completion handler로 비동기 작업을 제공했는데, 이제는 async / await로 더 자연스럽게 동일 기능을 제공한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tpHC2/btrgbJqhjuU/i6bn4iBUp62bNcXjilvofK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tpHC2/btrgbJqhjuU/i6bn4iBUp62bNcXjilvofK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tpHC2/btrgbJqhjuU/i6bn4iBUp62bNcXjilvofK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtpHC2%2FbtrgbJqhjuU%2Fi6bn4iBUp62bNcXjilvofK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;492&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 기존에 completion handler로 비동기 작업을 제공하던 API들은 다들 비슷한 모양을 가지고 있는데요, 이걸 이제 async / await를 사용해서 바꾸면 아래와 같아집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b47EtS/btrgk8ONfdi/iGbmDdPIv9Y0r4ptY7m2Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b47EtS/btrgk8ONfdi/iGbmDdPIv9Y0r4ptY7m2Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b47EtS/btrgk8ONfdi/iGbmDdPIv9Y0r4ptY7m2Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb47EtS%2Fbtrgk8ONfdi%2FiGbmDdPIv9Y0r4ptY7m2Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;316&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 자체가 훨씬 간단하고 직관적으로 표현되는 것을 볼 수 있습니다! 컴파일러가 예전 코드를 사용하면 async / await를 사용하라고 제안한다고 하니... 기존 코드에 노란색 경고가 많아질 것 같은 느낌이 드네요. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Delegate 패턴을 사용할 때도 completion handler를 많이 사용하는데 이러한 부분들도 모두 async / await를 지원하도록 개선했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기존 API들이 많이 변화되었는데 각각의 API에 대한 자세한 내용들은 아래 영상들을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10095&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Use async/await with URLSession&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10017&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bring Core Data concurrency to Swift and SwiftUI&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10146&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;What&amp;rsquo;s new in AVFoundation&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10054&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;What's new in AppKit&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Async alternatives and continuations&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 async / await가 생겨났고 많은 API들이 이를 지원하지만, 개발을 하다 보면 어쩔 수 없이 비동기 코드를 직접 만들어야 하는 경우도 생길 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtAtaD/btrgbKbBea9/ovhcmAouxsMSyDHnHlfz7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtAtaD/btrgbKbBea9/ovhcmAouxsMSyDHnHlfz7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtAtaD/btrgbKbBea9/ovhcmAouxsMSyDHnHlfz7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtAtaD%2FbtrgbKbBea9%2FovhcmAouxsMSyDHnHlfz7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1186&quot; height=&quot;656&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 getPersistentPosts 함수를 사용하여 Core Data의 저장소에 존재하는 모든 게시물을 검색합니다. 이 함수는 앱 전체에서 사용하고 있어서 모두 async / await를 적용한다면 큰 변화를 얻을 수 있을 것 같습니다. 이 함수는 NSAsynchronousFetchResult를 사용하고 있어서 비동기 작업을 하고 있으니 직접 비동기 코드로 만들어봅시다.&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;1342&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR2f1r/btrgiaGSQqB/JDZa3at8tuqMI5nfcjtAB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR2f1r/btrgiaGSQqB/JDZa3at8tuqMI5nfcjtAB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR2f1r/btrgiaGSQqB/JDZa3at8tuqMI5nfcjtAB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR2f1r%2FbtrgiaGSQqB%2FJDZa3at8tuqMI5nfcjtAB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;704&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 위와 같이 async 함수로 만들고 오류도 처리하기 위해 throws도 추가했습니다. 여기서 아까의 getPersistentPosts 함수를 호출하면 completion handler를 처리해줘야 되는데... async 함수는 일시 정지되면 시스템에 스레드를 반환하기 때문에 다음 작업을 수행할 적절한 시점을 찾아야 합니다.&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;1390&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FIDSY/btrgjzslUiW/9lZJzhOhBed93cKupSNphk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FIDSY/btrgjzslUiW/9lZJzhOhBed93cKupSNphk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FIDSY/btrgjzslUiW/9lZJzhOhBed93cKupSNphk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFIDSY%2FbtrgjzslUiW%2F9lZJzhOhBed93cKupSNphk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1390&quot; height=&quot;478&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 자세히 살펴보면 위의 그림과 같습니다. persistentPosts가 호출되면 Core Data를 호출합니다. 그런 뒤 Core Data에서 작업을 마치면 getPersistentPosts의 completion handler를 호출하게 됩니다. 코드에 await 키워드만 없었을 뿐 동작 방식은 비슷한 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 메서드 호출자는 함수 호출의 결과를 기다리고 결과를 얻은 뒤 할 일을 클로저(completion handler)로 정해줍니다. 함수 호출이 완료되면 호출자는 completion handler를 호출해서 비동기 기능을 처리합니다. 이를 명확하게 처리하기 위해&amp;nbsp;Swift에서는 높은 수준의 안전한 방식으로 작업을 create, manage, resume 할 수 있는 continuation 기능을 제공하며 다시 코드로 돌아가서 이를 적용해보면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvYNrc/btrgdDv38Co/KzYNthLgG96cf8OT6ksKok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvYNrc/btrgdDv38Co/KzYNthLgG96cf8OT6ksKok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvYNrc/btrgdDv38Co/KzYNthLgG96cf8OT6ksKok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvYNrc%2FbtrgdDv38Co%2FKzYNthLgG96cf8OT6ksKok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;440&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드처럼 withCheckedThrowingContinuation 함수를 사용하면 비동기 Swift 함수를 오류를 처리할 수 있도록 해줍니다. 만약 오류를 처리하고 싶지 않다면 withCheckContinuation을 사용하면 됩니다. 이러한 함수는 일시 정지된 async 함수를 다시 시작하는 데 사용할 수 있는 continuation value에 접근하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Continuation value는 completion handler의 처리하기 위한 값을 제공합니다. 이렇게 하면 completion handler와 async 함수를 함께 사용할 수 있습니다. 이렇게 async 함수의 실행을 수동으로 제어하기 위해 continuation을 사용할 때 주의할 점들이 있습니다.&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;1190&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhjnRD/btrgdHlaupz/yQdaX5XUb4zejuediHO0r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhjnRD/btrgdHlaupz/yQdaX5XUb4zejuediHO0r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhjnRD/btrgdHlaupz/yQdaX5XUb4zejuediHO0r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhjnRD%2FbtrgdHlaupz%2FyQdaX5XUb4zejuediHO0r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1190&quot; height=&quot;684&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Continuation을 사용할 때 주의할 점은 resume을 한 번만 호출해야 한다는 점입니다. 호출하지 않아서도 안되고, 위와 같이 두 번 호출해서도 안됩니다. 특히 위와 같이 resume을 여러 번 호출하면 Swift는 런타임에서 fatal error를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 checked continuation을 사용할 수 있는 부분은 이벤트 기반의 API가 많은 부분입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Z14v/btrgdHrYmR1/MGQMzF1b2Oh47i7ekMDFsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Z14v/btrgdHrYmR1/MGQMzF1b2Oh47i7ekMDFsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Z14v/btrgdHrYmR1/MGQMzF1b2Oh47i7ekMDFsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Z14v%2FbtrgdHrYmR1%2FMGQMzF1b2Oh47i7ekMDFsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1276&quot; height=&quot;736&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 특수한 지점에서 앱에 알리고 적절하게 응답할 수 있도록 delegate calback을 많이 사용하는데요, async / await를 사용하기 위해서는 continuation을 저장하고 나중에 resume 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Continuation을 포함해서 Swift 동시성의 lower level에 대한 자세한 내용은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10254&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift concurrency: Behind the scenes&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsCaEh/btrgh9gSfgB/kPrTJWgmBpFAmUW0hgVFa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsCaEh/btrgh9gSfgB/kPrTJWgmBpFAmUW0hgVFa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsCaEh/btrgh9gSfgB/kPrTJWgmBpFAmUW0hgVFa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsCaEh%2Fbtrgh9gSfgB%2FkPrTJWgmBpFAmUW0hgVFa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;378&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Swift에 추가된 async / await에 대해 알아봤고 정리를 해보면..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async / await가 런타임에서 작동하는 방식과 앱과 프레임워크에서 사용하는 방법을 알아봤습니다.&lt;/li&gt;
&lt;li&gt;SDK에서 생성할 수 있는 async API가 있고, 기존 코드를 async로 연결하는 방법을 알아봤습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이번 영상은 마무리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상을 통해 Swift 5.5에 추가된 async / await의 사용법과 동작 원리를 어느 정도 이해할 수 있었고, 다음으로 보고 싶은 영상 정보도 알 수 있어 좋았습니다.☺️ 뭔가 async / await가 중요해 보여서 깊게 공부하고 싶은 생각이 드는 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘 WWDC 영상인 &quot;Meet async / await in Swift&quot;는 여기까지 정리하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Apple/WWDC 2021</category>
      <category>2021</category>
      <category>5.5</category>
      <category>async</category>
      <category>Await</category>
      <category>concurrency</category>
      <category>continuation</category>
      <category>IOS</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/266</guid>
      <comments>https://icksw.tistory.com/266#entry266comment</comments>
      <pubDate>Wed, 29 Sep 2021 02:21:49 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2021] What's new in Swift</title>
      <link>https://icksw.tistory.com/265</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 WWDC 2021의 &quot;What's new in Swift&quot;라는 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10192/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;영상&lt;/a&gt;을 정리한 글을 써보려고 합니다. 영상 제목에서도 알 수 있듯 Swift의 새로운 기능들을 간단하게 알려주는? 영상이었습니다. 그래서 새로운 기능들은 세부적인 영상으로 공부를 또 해야 할 듯합니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;What's new in Swift&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 5.5에서 추가된 내용은 아래와 같다고합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Diversity&lt;/li&gt;
&lt;li&gt;Update on Swift Packages&lt;/li&gt;
&lt;li&gt;Update on Swift on server&lt;/li&gt;
&lt;li&gt;Developer experience improvements&lt;/li&gt;
&lt;li&gt;Ergonomic improvements&lt;/li&gt;
&lt;li&gt;Asynchronous and concurrent programming&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 순서대로 알아보도록 한답니다.&lt;/p&gt;
&lt;h1&gt;Diversity&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Diversity 파트에는 특별한 건 나오지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://swift.org/diversity&quot;&gt;swift.org/diversity&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1632035310068&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;Swift.org&quot; data-og-description=&quot;Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns.&quot; data-og-host=&quot;swift.org&quot; data-og-source-url=&quot;http://swift.org/diversity&quot; data-og-url=&quot;https://swift.org&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://swift.org/diversity&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://swift.org/diversity&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;Swift.org&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;swift.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Swift 개발자 커뮤니티가 생겼으니 홍보하는 내용과 Swift 멘토링 프로그램이 생겼으니 위 사이트에서 활용해보라는 내용 정도였어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Update on Swift packages&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift Package Index&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift package는 점점 자주 사용하게 되는 것 같은데, 이번에 Swift 커뮤니티 사용자들은 Swift package를 찾는데 도움이 되는 솔루션이 생겼다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;598&quot; width=&quot;724&quot; height=&quot;387&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNjt6K/btrfuPv7lEH/VnTJULwZMTaRhgPhRtzB21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNjt6K/btrfuPv7lEH/VnTJULwZMTaRhgPhRtzB21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNjt6K/btrfuPv7lEH/VnTJULwZMTaRhgPhRtzB21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNjt6K%2FbtrfuPv7lEH%2FVnTJULwZMTaRhgPhRtzB21%2Fimg.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;598&quot; width=&quot;724&quot; height=&quot;387&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift Package Index는 Swift Package Manager가 지원하는 패키지를 찾는데 도움이 되는 커뮤니티에서 만든 페이지라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 활용해서 Swift 5.5, Xcode 13의 기능으로 패키지를 쉽게 찾고 접근할 수 있는 방법을 제공한다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 패키지를 찾는데 도움이 될 것 같긴 합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift Package Collections&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선별된 Swift package 목록을 제공하는 Swift Package Collections를 제공한다고 합니다. 이걸 사용하면 더 이상 인터넷에서 패키지를 검색하거나 URL을 복사 붙여 넣기로 추가할 필요가 없다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;636&quot; width=&quot;684&quot; height=&quot;372&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caI4nT/btrfpBsltgY/O0BrWS1kKlQogHJC51cccK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caI4nT/btrfpBsltgY/O0BrWS1kKlQogHJC51cccK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caI4nT/btrfpBsltgY/O0BrWS1kKlQogHJC51cccK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaI4nT%2FbtrfpBsltgY%2FO0BrWS1kKlQogHJC51cccK%2Fimg.png&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;636&quot; width=&quot;684&quot; height=&quot;372&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Package Collection은 어디서나 게시할 수 있는 JSON 파일이고 선별된 패키지 목록을 제공하기 위한 기능이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 학교에서 교수님들이 필요한 패키지 세트를 만들거나, 특정 도메인이나 작업, 혹은 조직에 사용할 패키지 컬렉션을 만들 수 있다고 합니다. 이에 대한 자세한 내용은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10197&quot;&gt;Discover and curate Swift Packages using Collections&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;380&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AVCgZ/btrfoSg2qBf/NA8fNnrwGlBJDopB48R8k0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AVCgZ/btrfoSg2qBf/NA8fNnrwGlBJDopB48R8k0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AVCgZ/btrfoSg2qBf/NA8fNnrwGlBJDopB48R8k0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/AVCgZ/btrfoSg2qBf/NA8fNnrwGlBJDopB48R8k0/img.gif&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;380&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음엔 Xcode에서 실제로 Package Collection을 사용하는 영상을 소개하는데.. 엄청 편해 보였습니다. 필요한 패키지를 알아서 다운받아서 import 해주는데 위에 나온 사례들에서는 정말 유용하게 쓸 수 있겠다 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 검색 기능을 활용해서 필요한 패키지를 찾을 수도 있으니 확실히 잘 활용하면 좋은 기능이라는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;New Swift Packages&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;278&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV4s2O/btrfw4s1pb2/yLnSjatUnxVpHwJlgPWYuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV4s2O/btrfw4s1pb2/yLnSjatUnxVpHwJlgPWYuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV4s2O/btrfw4s1pb2/yLnSjatUnxVpHwJlgPWYuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV4s2O%2Fbtrfw4s1pb2%2FyLnSjatUnxVpHwJlgPWYuK%2Fimg.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;278&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 기존에 존재하던 Swift 패키지들 외에도 위에서 표시된 4개의 새로운 패키지를 출시했다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift Algorithms&lt;/li&gt;
&lt;li&gt;Swift Atomics&lt;/li&gt;
&lt;li&gt;Swift Collections&lt;/li&gt;
&lt;li&gt;Swift System&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 언급하는 건 위 4개가 아닌 아래 5개입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift Collections&lt;/li&gt;
&lt;li&gt;Swift Algorithms&lt;/li&gt;
&lt;li&gt;Swift System&lt;/li&gt;
&lt;li&gt;Swift Numerics&lt;/li&gt;
&lt;li&gt;Swift Argument Parser&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만 봐도 코딩 테스트할 때 유용할 녀석들도 보이네요.. 실제로 테스트에서 사용 가능할진 모르지만..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift Collections Package&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;368&quot; width=&quot;425&quot; height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tr5fS/btrfw5rWH4N/7KngD43ZDwF2p7ThhCjHo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tr5fS/btrfw5rWH4N/7KngD43ZDwF2p7ThhCjHo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tr5fS/btrfw5rWH4N/7KngD43ZDwF2p7ThhCjHo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftr5fS%2Fbtrfw5rWH4N%2F7KngD43ZDwF2p7ThhCjHo0%2Fimg.png&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;368&quot; width=&quot;425&quot; height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 새로 생긴 Swift Collection은 Swift에서 사용할 수 있는 자료구조를 확장해주는 패키지인데, 패키지를 사용하면 위의 세 자료구조가 새롭게 사용 가능하다고 합니다. 이 정도는 언어 자체에 추가를 해주는 게..  직접 Deque를 만들어서 사용한 기억과 OrderedSet은 &amp;nbsp;Core Data를 사용해보며 이미 사용한 경험이 있는 거 같은데? 뭔가 이상하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmNUnY/btrfpCrg8y2/67SERXHSiyWRKotjG8xSKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmNUnY/btrfpCrg8y2/67SERXHSiyWRKotjG8xSKk/img.png&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;478&quot; width=&quot;611&quot; height=&quot;250&quot; style=&quot;width: 32.5726948405897%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmNUnY/btrfpCrg8y2/67SERXHSiyWRKotjG8xSKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmNUnY%2FbtrfpCrg8y2%2F67SERXHSiyWRKotjG8xSKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNjCtT/btrfpCktuQw/gjs3gHcUzBvs1fYMCevj0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNjCtT/btrfpCktuQw/gjs3gHcUzBvs1fYMCevj0k/img.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;482&quot; style=&quot;width: 32.468035261465765%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNjCtT/btrfpCktuQw/gjs3gHcUzBvs1fYMCevj0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNjCtT%2FbtrfpCktuQw%2Fgjs3gHcUzBvs1fYMCevj0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;482&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IaaWT/btrftnfBnfl/6Bp5WUtDqULKoku3VEddf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IaaWT/btrftnfBnfl/6Bp5WUtDqULKoku3VEddf1/img.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;482&quot; style=&quot;width: 32.63368850259569%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IaaWT/btrftnfBnfl/6Bp5WUtDqULKoku3VEddf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIaaWT%2FbtrftnfBnfl%2F6Bp5WUtDqULKoku3VEddf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1182&quot; height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 이렇게 3개의 자료구조를 패키지를 사용하면 사용하게 해 준다고 자랑하며 사용법도 알려줍니다.  &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift Algorithms Package&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 Swift Algorithm 패키지를 자랑합니다. 이건 Sequence, Collection 알고리즘의 새로운 오픈소스입니다.&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-origin-width=&quot;1114&quot; data-origin-height=&quot;628&quot; width=&quot;743&quot; height=&quot;419&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9JBX7/btrfp2wt2Ll/If8rgfFiNkAcOlvsVGrVjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9JBX7/btrfp2wt2Ll/If8rgfFiNkAcOlvsVGrVjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9JBX7/btrfp2wt2Ll/If8rgfFiNkAcOlvsVGrVjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9JBX7%2Fbtrfp2wt2Ll%2FIf8rgfFiNkAcOlvsVGrVjK%2Fimg.png&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;628&quot; width=&quot;743&quot; height=&quot;419&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 존재하는 모든 알고리즘을 제공한다고 합니다. 그중 감동?적인 부분은 순열 조합을 만드는 기능을 제공한다는 점이었습니다. 코딩 테스트에서 자주 활용되는 알고리즘인데.. 매번 구현하느라 슬펐던 기억이 있네요.  이에 대한 더 자세한 내용은 아래 영상을 참고하면 된다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10256&quot;&gt;Meet the Swift Algorithms and Collections packages&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift System Package&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;650&quot; width=&quot;629&quot; height=&quot;346&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxJVse/btrfpgB0wVY/Za8w2uOQUILKWFgZizVPk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxJVse/btrfpgB0wVY/Za8w2uOQUILKWFgZizVPk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxJVse/btrfpgB0wVY/Za8w2uOQUILKWFgZizVPk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxJVse%2FbtrfpgB0wVY%2FZa8w2uOQUILKWFgZizVPk1%2Fimg.png&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;650&quot; width=&quot;629&quot; height=&quot;346&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Swift System입니다. 작년 가을에 발표했다고 하는 이 패키지는 system call의 저수준 인터페이스를 제공하는 패키지라고 하며 Apple, Linux, Window에서 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가된 기능은 각 OS 별로 파일 경로를 잘 제공하는 기능이라고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift Numerics Package&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;686&quot; width=&quot;700&quot; height=&quot;395&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dr0mFp/btrfw3VbFtJ/yattLspym94EvsZfuQmQrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dr0mFp/btrfw3VbFtJ/yattLspym94EvsZfuQmQrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dr0mFp/btrfw3VbFtJ/yattLspym94EvsZfuQmQrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdr0mFp%2Fbtrfw3VbFtJ%2FyattLspym94EvsZfuQmQrK%2Fimg.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;686&quot; width=&quot;700&quot; height=&quot;395&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Swift Numerics입니다. 작년에 iOS, tvOS, watchOS에 Float16이 추가되었다고 하네요. 오.. 없는 거도 몰랐던 1인입니다. 어쨌든 그랬었는데, 올해는 Apple Silicon macs에도 Float 16, Flaot 16 복소수를 사용할 수 있게 되었다고 합니다. 또 로그, 사인, 코사인과 같은 모든 기능에 대한 복소수를 지원한다고 합니다. 이를 Swift로 작성해서 더 잘 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Swift ArgumentParser Packages&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;370&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLB4UO/btrftmVfCxs/Z1MknnZ9J8Z8cowZ2xMzDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLB4UO/btrftmVfCxs/Z1MknnZ9J8Z8cowZ2xMzDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLB4UO/btrftmVfCxs/Z1MknnZ9J8Z8cowZ2xMzDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLB4UO%2FbtrftmVfCxs%2FZ1MknnZ9J8Z8cowZ2xMzDK%2Fimg.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;370&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Swift ArgumentParser입니다. 원래 존재했던 패키지를 계속 개선해서 Fish Shell용 스크립트 생성 기능, 짧은 옵션 결합 기능, 오류 메시지 개선 기능이 추가되었다고 합니다.&lt;/p&gt;
&lt;h1&gt;Update on Swift on server&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 섹션으로 서버 개발에서 Swift를 지원하기 위해 진행된 작업들을 소개한다고 합니다. 작년에 Amazon linux를 포함한 여러 플랫폼에 대한 지원을 추가했다는데, 올해도 Swift 서버 앱의 성능과 기능 향상을 위해 작업을 했다고 합니다.&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-origin-width=&quot;564&quot; data-origin-height=&quot;306&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O63aD/btrfpBlC3Hy/bkuCLZYOVKBkMcM9WuTZak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O63aD/btrfpBlC3Hy/bkuCLZYOVKBkMcM9WuTZak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O63aD/btrfpBlC3Hy/bkuCLZYOVKBkMcM9WuTZak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO63aD%2FbtrfpBlC3Hy%2FbkuCLZYOVKBkMcM9WuTZak%2Fimg.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;306&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향상된 내용은 위와 같이 3개라고 합니다. Linux에서 정적 링크를 활성화해서 앱 시작 시간을 단축하고 단일 파일로 배포할 수 있는 서버 앱 배포를 간소화했다고 합니다. 또한 Swift 5.5에서는 Linux에서 사용되는 JSON 인코딩 디코딩이 처음부터 다시 구현돼서 성능이 향상되었다고 합니다. 마지막으로 AWS Lambda 런타임 라이브러리 자체의 성능을 개선하고 최적화했다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epaApH/btrfqDQJOIP/n2Xp5z0G1P94FbrXz504T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epaApH/btrfqDQJOIP/n2Xp5z0G1P94FbrXz504T1/img.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;416&quot; style=&quot;width: 46.685524512935075%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epaApH/btrfqDQJOIP/n2Xp5z0G1P94FbrXz504T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FepaApH%2FbtrfqDQJOIP%2Fn2Xp5z0G1P94FbrXz504T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQoZjB/btrfseJXv1M/kuZusvestYgeJ643mKf4kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQoZjB/btrfseJXv1M/kuZusvestYgeJ643mKf4kk/img.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;408&quot; style=&quot;width: 52.151684789390515%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQoZjB/btrfseJXv1M/kuZusvestYgeJ643mKf4kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQoZjB%2FbtrfseJXv1M%2FkuZusvestYgeJ643mKf4kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1146&quot; height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같이 빨라졌다고 합니다. &lt;/p&gt;
&lt;h1&gt;Developer experience improvements&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DocC&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 개발자 경험을 개선하기 위해 개발 문서 부분을 개선했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;256&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLWWvx/btrfqiFHhrG/9wbqfecqvDnwk335lhynk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLWWvx/btrfqiFHhrG/9wbqfecqvDnwk335lhynk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLWWvx/btrfqiFHhrG/9wbqfecqvDnwk335lhynk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLWWvx%2FbtrfqiFHhrG%2F9wbqfecqvDnwk335lhynk0%2Fimg.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;256&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode 13 내부에 통합된 문서 컴파일러인 DocC가 추가되었다고 합니다. 이걸 사용해서 좋은 문서를 작성하고 공유하는 게 편리해졌다고 해요. DocC는 마크다운 주석과 같이 이미 알고 있는 도구와 기술을 사용하여 문서를 쉽게 작성할 수 있도록 도와준다고 합니다. 한 번쯤 사용해보고 싶은 기능이네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DocC에 대한 자세한 내용은 아래의 4개 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-hires-status=&quot;pending&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10166&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Meet DocC documentation in Xcode&lt;/a&gt;&lt;/li&gt;
&lt;li data-hires-status=&quot;pending&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Elevate yout DocC documentation in Xcode&lt;/a&gt;&lt;/li&gt;
&lt;li data-hires-status=&quot;pending&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10236&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Host and automate your DooC documentation&lt;/a&gt;&lt;/li&gt;
&lt;li data-hires-status=&quot;pending&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Build Interactive turorials using DocC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 DocC가 올해 말에 오픈 소스가 될 것이라고 합니다. 그래서 Swift 지원 플랫폼에서는 좋은 문서를 쉽게 생성할 수 있을 거라고 하네요!&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;Swift Type Checker&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;656&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UrYFk/btrftoMlIF2/PiRJpbEq6dRmkRKeQfb9V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UrYFk/btrftoMlIF2/PiRJpbEq6dRmkRKeQfb9V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UrYFk/btrftoMlIF2/PiRJpbEq6dRmkRKeQfb9V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUrYFk%2FbtrftoMlIF2%2FPiRJpbEq6dRmkRKeQfb9V0%2Fimg.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;656&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 5.5에서는 type checker의 품질 및 성능 개선이 되었다고 합니다. 그 결과 코드를 컴파일할 때 자주 보던 &quot;expression too complex&quot; 오류가 더 적게 표시된다고 해요. 또한 array literal의 type check 성능도 좋아졌다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위의 사진과 같이 기존에는 오류를 발생하던 부분도 모두 정상적으로 작동한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Build Improvements&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;514&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqP9p3/btrfqC5qc8c/lk8TOpkdcVkD81dLyKXfe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqP9p3/btrfqC5qc8c/lk8TOpkdcVkD81dLyKXfe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqP9p3/btrfqC5qc8c/lk8TOpkdcVkD81dLyKXfe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqP9p3%2FbtrfqC5qc8c%2Flk8TOpkdcVkD81dLyKXfe0%2Fimg.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;514&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;incremental import를 제공합니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 모듈이 변경될 때 모듈을 import 하는 모든 소스 파일을 더 이상 빌드하지 않는다고 합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;module dependency graph를 미리 계산합니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경된 항목만 incremental build를 빠르게 시작한다고 합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;extension과 함께 작동하도록 selective recompilation을 확장합니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;extension을 사용했을 때 더 적은 수의 재컴파일을 의미한다고 합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftDriver라는 오픈 소스 프로젝트를 예로 들면, 아래와 같이 빌드 시간이 개선되었다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lGJpB/btrfoSBoOY6/jL4YKg6eVMTQghOTuVkYCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lGJpB/btrfoSBoOY6/jL4YKg6eVMTQghOTuVkYCK/img.png&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;686&quot; style=&quot;width: 50.40094307200786%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lGJpB/btrfoSBoOY6/jL4YKg6eVMTQghOTuVkYCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlGJpB%2FbtrfoSBoOY6%2FjL4YKg6eVMTQghOTuVkYCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1112&quot; height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb9UdN/btrfpgWhROd/RKruFlNXKqZuFuqGl4FFi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb9UdN/btrfpgWhROd/RKruFlNXKqZuFuqGl4FFi1/img.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;692&quot; style=&quot;width: 48.436266230317706%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb9UdN/btrfpgWhROd/RKruFlNXKqZuFuqGl4FFi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb9UdN%2FbtrfpgWhROd%2FRKruFlNXKqZuFuqGl4FFi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1078&quot; height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Memory Management&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Swift에서 메모리 관리를 보다 효율적으로 개선했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 클래스 인스턴스는 ARC를 사용해서 객체의 참조수를 추적하게 됩니다. 이렇게 Swift가 알아서 메모리를 관리해주는데, 이번에 참조 유지, 해제 작업 수를 크게 줄일 수 있도록 컴파일러 내부에서 참조를 추적하는 새로운 방법을 도입했다고 합니다. 이를 통해 성능 향상과 코드 크기를 줄였다고 하네요.&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-origin-width=&quot;1182&quot; data-origin-height=&quot;662&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZLor4/btrfs50cFZu/fOKwDpKb5ySbXIGWrIG1C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZLor4/btrfs50cFZu/fOKwDpKb5ySbXIGWrIG1C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZLor4/btrfs50cFZu/fOKwDpKb5ySbXIGWrIG1C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZLor4%2Fbtrfs50cFZu%2FfOKwDpKb5ySbXIGWrIG1C1%2Fimg.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;662&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 새로운 기능이 코드에 미치는 영향을 확인할 수 있는 Xcode 설정인 Optimize Object Lifetimes를 추가했다고 합니다. 이에 대한 좀 더 자세한 내용은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10216/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ARC in Swift: Basics and beyond&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Ergonomic improvements&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 섹션에서는 Swift의 여러 개선사항에 대해 알아봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;654&quot; width=&quot;426&quot; height=&quot;498&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l52a5/btrfpPRtHQW/ziWrLwu9D1w2WJ7HMBk9bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l52a5/btrfpPRtHQW/ziWrLwu9D1w2WJ7HMBk9bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l52a5/btrfpPRtHQW/ziWrLwu9D1w2WJ7HMBk9bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl52a5%2FbtrfpPRtHQW%2FziWrLwu9D1w2WJ7HMBk9bk%2Fimg.png&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;654&quot; width=&quot;426&quot; height=&quot;498&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진에 언급된 내용들이 변경된 내용들이라고 합니다.&amp;nbsp;SE 번호는 Swift Evolution Proposals를 식별합니다. 각각의 Proposals는 Swift 커뮤니티에서 논의되어 결정된 내용이라고 하네요. 이번 영상에서는 이들 중 몇 가지를 살펴본다고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Result Builders&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;632&quot; width=&quot;327&quot; height=&quot;366&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjEQ8E/btrftosgFYW/VuEPHTrbCenZh7HDYZWVF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjEQ8E/btrftosgFYW/VuEPHTrbCenZh7HDYZWVF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjEQ8E/btrftosgFYW/VuEPHTrbCenZh7HDYZWVF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjEQ8E%2FbtrftosgFYW%2FVuEPHTrbCenZh7HDYZWVF0%2Fimg.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;632&quot; width=&quot;327&quot; height=&quot;366&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 Result Builder입니다. SwiftUI가 처음 발표되었을 때 복잡한 객체 계층을 빠르고 쉽게 설명할 수 있는 새로운 문법을 도입했었는데, 이러한 문법은 Swift Evolution 프로세스를 통해 표준화되어 다양한 곳에서 더 쉽게 사용할 수 있다고 합니다. 이 기술을 자세히 알고 싶다면 아래 영상을 보라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10253&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Write a DSL in Swift using reuslt builders&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Enum Codable synthesis&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codable 프로토콜은 데이터를 직렬 화하는 편리한 방법이지만, 오랫동안 생략된 부분이 존재해서 사용에 어려움이 있었다고 합니다.  &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-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; width=&quot;592&quot; height=&quot;320&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRycDc/btrfxO4PGi3/9fOAXt4B9W5EYxP1NKntgk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRycDc/btrfxO4PGi3/9fOAXt4B9W5EYxP1NKntgk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRycDc/btrfxO4PGi3/9fOAXt4B9W5EYxP1NKntgk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bRycDc/btrfxO4PGi3/9fOAXt4B9W5EYxP1NKntgk/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; width=&quot;592&quot; height=&quot;320&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2 case를 갖는 Enum의 경우 Codable을 준수하도록 하려면 위처럼 코딩을 했어야 했는데, 이제 Codable 프로토콜만 채택해주면 컴파일러가 알아서 해준다고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Flexible static member lookup&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift type checker에 개선사항이 있다고 합니다. Swift의 타입 추론은 중복된 타입 정보를 생략할 수 있음을 의미하는데요, 예를 들어 아래 코드를 보겠습니다.&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-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3uGVb/btrfuKBDGwb/7ZtQLv4d5nWYx9lDiIwTQ1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3uGVb/btrfuKBDGwb/7ZtQLv4d5nWYx9lDiIwTQ1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3uGVb/btrfuKBDGwb/7ZtQLv4d5nWYx9lDiIwTQ1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/3uGVb/btrfuKBDGwb/7ZtQLv4d5nWYx9lDiIwTQ1/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coffee Enum에서 regular, decaf를 선언했는데 Coffee.regular로 써도 되지만, .regular로 줄일 수도 있습니다. 하지만 Structure도 다르게 표현될 수 있다고 합니다.&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-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tw1f6/btrfptA71ZM/uqJuVGxdmXwb8CD6h7ICVK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tw1f6/btrfptA71ZM/uqJuVGxdmXwb8CD6h7ICVK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tw1f6/btrfptA71ZM/uqJuVGxdmXwb8CD6h7ICVK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/tw1f6/btrfptA71ZM/uqJuVGxdmXwb8CD6h7ICVK/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같이 Coffee 프로토콜을 준수하는 RegularCoffee, Cappuccino 구조체가 있고 API에서 해당 타입의 인스턴스를 사용하려고 할 때 위와 같이 사용할 수 있다고 합니다. 이제 이렇게 Enum과 유사하게 사용할 수 있다고 하네요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Property wrappers on parameters&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Property wrapper도 개선되었다고 합니다. Property wrapper는 property에 일반적인 의미를 부여하기 위한 편리한 도구로 구조체에 @propertyWrapper 키워드로 고유한 Property Wrapper를 구현했습니다.&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-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdiIhP/btrfFGAOyBr/1xhmkBQaidFqLuzO2IkMC1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdiIhP/btrfFGAOyBr/1xhmkBQaidFqLuzO2IkMC1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdiIhP/btrfFGAOyBr/1xhmkBQaidFqLuzO2IkMC1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bdiIhP/btrfFGAOyBr/1xhmkBQaidFqLuzO2IkMC1/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 empty면 안된다는 요구 사항을 추가하는 예를 보도록 하겠습니다. 지금까지는 위와 같이 프로퍼티에 사용했었는데 이제부터는 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;이렇게만 보면 와닿지 않았는데, 다행히 영상에서 자세한 예를 보여줍니다. SwiftUI 코드로 만든 예를 보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;327&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7JxKS/btrfFECY9lQ/TZMw81iZaO9iWRAthoEQ0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7JxKS/btrfFECY9lQ/TZMw81iZaO9iWRAthoEQ0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7JxKS/btrfFECY9lQ/TZMw81iZaO9iWRAthoEQ0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7JxKS%2FbtrfFECY9lQ%2FTZMw81iZaO9iWRAthoEQ0K%2Fimg.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;327&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제는 Setting 타입의 배열과 Setting 타입의 값 옆에 토글이 있는 설정 목록을 보여주는 SwiftUI View입니다. 여기서 이번에 Swift 5.5에 추가된 기능으로 코드를 단순화해본다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Toggle() 생성자는 중복된 코드이므로 #if문과 toggleStyle과 같은 postfix 코드를 아래와 같이 수정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;319&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIKsVO/btrfPXnfj0i/CtsYg0AKUR1tzCIcNIjfkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIKsVO/btrfPXnfj0i/CtsYg0AKUR1tzCIcNIjfkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIKsVO/btrfPXnfj0i/CtsYg0AKUR1tzCIcNIjfkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIKsVO%2FbtrfPXnfj0i%2FCtsYg0AKUR1tzCIcNIjfkk%2Fimg.png&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;319&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그리고 아까 언급됐던 type checker 개선사항을 SwiftUI에서도 사용할 수 있는데, 이를 활용한 점 표기법으로 아래와 같이 코드를 줄일 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;307&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRczOa/btrfEnt3HdZ/zutXO2BRxCE5yiGPw2qrz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRczOa/btrfEnt3HdZ/zutXO2BRxCE5yiGPw2qrz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRczOa/btrfEnt3HdZ/zutXO2BRxCE5yiGPw2qrz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRczOa%2FbtrfEnt3HdZ%2FzutXO2BRxCE5yiGPw2qrz1%2Fimg.png&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;307&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. List와 해당 리스트의 클로저에서 배열을 인덱싱 하는 게 이상하므로 아래와 같이 수정하여 생성자에 배열의 값들을 직접 전달할 수 있으면 좋을 거 같은데. Property wrapper에 대한 새로운 기능을 통해 이를 가능하게 합니다.&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-origin-width=&quot;568&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOX0co/btrfRD2Uev9/rDKeNQ8fBjtqKUHX3jl70k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOX0co/btrfRD2Uev9/rDKeNQ8fBjtqKUHX3jl70k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOX0co/btrfRD2Uev9/rDKeNQ8fBjtqKUHX3jl70k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOX0co%2FbtrfRD2Uev9%2FrDKeNQ8fBjtqKUHX3jl70k%2Fimg.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Swift 컴파일러는 CGFloat과 Double을 알아서 잘 변환해줘서 아래와 같이 코드를 수정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNpiRp/btrfFFPt2mZ/GBVZeEg6bAm5EdZOBvbm8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNpiRp/btrfFFPt2mZ/GBVZeEg6bAm5EdZOBvbm8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNpiRp/btrfFFPt2mZ/GBVZeEg6bAm5EdZOBvbm8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNpiRp%2FbtrfFFPt2mZ%2FGBVZeEg6bAm5EdZOBvbm8K%2Fimg.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 예제 말고도 SwiftUI 프로그래밍에 개선된 여러 변화는 아래 영상을 참고하면 된다고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10018&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;What's new in SwiftUI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Asynchronous and concurrent programming&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 가장 궁금했던 내용입니다. Swift 5.5부터는 드디어 async, await가 도입되면서 비동기, 동시 프로그래밍을 쉽게 작성할 수 있게 되었습니다. 아주 친절하게도 영상에서는 Asynchronous(비동기), Concurrent(동시)부터 알려준다고 합니다.&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-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tKxTn/btrfRpjhJ7N/2D5kzCdRBbrerivlQ8ia71/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tKxTn/btrfRpjhJ7N/2D5kzCdRBbrerivlQ8ia71/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tKxTn/btrfRpjhJ7N/2D5kzCdRBbrerivlQ8ia71/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/tKxTn/btrfRpjhJ7N/2D5kzCdRBbrerivlQ8ia71/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zjGYr/btrfPJvOeg7/Hu8wIbAuQUqY47VVysRujK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zjGYr/btrfPJvOeg7/Hu8wIbAuQUqY47VVysRujK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zjGYr/btrfPJvOeg7/Hu8wIbAuQUqY47VVysRujK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zjGYr/btrfPJvOeg7/Hu8wIbAuQUqY47VVysRujK/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 진행되는 프로그램을 Asynchronous(비동기)라고 합니다. 비동기 작업의 예로는 네트워킹 작업이 있는데요, 어떤 데이터를 다운 받는 작업은 비교적 길 수 있는데, 완전히 받을 때까지 기다렸다가 다음 작업을 시작해야 합니다. 즉 다운받는 동안에 다른 작업을 실행하지는 않아요.&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-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnlO7W/btrfMPpZZgI/MKKda3Ud7oY8O61p7Pok00/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnlO7W/btrfMPpZZgI/MKKda3Ud7oY8O61p7Pok00/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnlO7W/btrfMPpZZgI/MKKda3Ud7oY8O61p7Pok00/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cnlO7W/btrfMPpZZgI/MKKda3Ud7oY8O61p7Pok00/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 Concurrent 작업은 두 개 이상의 코드를 동시에 실행할 수 있습니다. 아까와 동일하게 데이터를 다운 받는다고 해도, 다운받는 동안 다른 작업도 실행할 수 있는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;256&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mZ1Tu/btrfIXPzTiV/w4noB3wwh8L3JfdzLXn88K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mZ1Tu/btrfIXPzTiV/w4noB3wwh8L3JfdzLXn88K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mZ1Tu/btrfIXPzTiV/w4noB3wwh8L3JfdzLXn88K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmZ1Tu%2FbtrfIXPzTiV%2Fw4noB3wwh8L3JfdzLXn88K%2Fimg.png&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;256&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드가 Swift 5.5 이전의 비동기 코드입니다. 네트워크 호출을 위해 URLSession을 사용하고 dataTask 메서드로 비동기 작업을 수행합니다. 완료되면 클로저를 실행하게 되죠. 이렇게 작성된 코드는 실행 순서와 코드 순서가 달라서 조금 어색하게 보였어요. 그리고 이렇게 completion handler를 사용하면 try / catch를 사용한 오류 처리도 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 이제 Swift 5.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 alignCenter&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTKP1v/btrfQEudr63/T2S4xOoC8EskfzgpBk97Dk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTKP1v/btrfQEudr63/T2S4xOoC8EskfzgpBk97Dk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTKP1v/btrfQEudr63/T2S4xOoC8EskfzgpBk97Dk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dTKP1v/btrfQEudr63/T2S4xOoC8EskfzgpBk97Dk/img.gif&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;332&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위와 같이 기존 코드를 수정해줍니다. 해당 작업으로 우리는 데이터를 얻고 싶은 건데, HTTP 통신이니 Response도 알아야 합니다. 따라서 위와 같이 튜플로 가지고 옵니다.&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-origin-width=&quot;1012&quot; data-origin-height=&quot;66&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lpO1V/btrfKCkaLQH/33H7JoQC28GCLSC2ARuAH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lpO1V/btrfKCkaLQH/33H7JoQC28GCLSC2ARuAH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lpO1V/btrfKCkaLQH/33H7JoQC28GCLSC2ARuAH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlpO1V%2FbtrfKCkaLQH%2F33H7JoQC28GCLSC2ARuAH0%2Fimg.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;66&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 위와 같이 수정하면 try / catch 문을 활용한 오류 처리도 가능하게 됩니다.&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-origin-width=&quot;948&quot; data-origin-height=&quot;90&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dp6wdR/btrfO3BvaAP/iT0KExZfLI2ZsbyLMAQQq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dp6wdR/btrfO3BvaAP/iT0KExZfLI2ZsbyLMAQQq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dp6wdR/btrfO3BvaAP/iT0KExZfLI2ZsbyLMAQQq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdp6wdR%2FbtrfO3BvaAP%2FiT0KExZfLI2ZsbyLMAQQq0%2Fimg.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;90&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기존 작업과 동일하게 동작하도록 만들기 위해서 data 메서드가 시작되면 해당 작업이 완료될 때까지 다른 작업이 진행되지 않도록 해야 하는데, 이 때는 위와 같이 await 키워드만 써주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로는 아까와 동일한 작업을 Swift 5.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 alignCenter&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;510&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRjx4/btrfQokDhdw/aHKRRtSdzWMio98dk8PQb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRjx4/btrfQokDhdw/aHKRRtSdzWMio98dk8PQb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRjx4/btrfQokDhdw/aHKRRtSdzWMio98dk8PQb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRjx4%2FbtrfQokDhdw%2FaHKRRtSdzWMio98dk8PQb0%2Fimg.png&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;510&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 보다 코드가 순차적으로 진행되기 때문에 코드를 해석하는 게 훨씬 쉬워 보입니다. await 키워드는 fetchImage() 함수가 일시 중단될 수 있는 지점을 나타내고, 다시 진행할 수 있는 이벤트가 발생할 때까지 기다립니다. 위 코드의 경우 네트워크 요청이 실해하면 Error를 던지고 그렇지 않으면 image를 반환하게 됩니다. 그리고 이 과정에서 스레드를 차단하지 않기 때문에 다른 작업을 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 async가 갑자기 함수 선언부에 생기긴 했는데 async / await 키워드는 throw, try 구문과 비슷하게 쓰인다고 보면 된다고 합니다.&lt;/p&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 / await에 대해 알고 싶다면 아래 영상을 보라고 하니 꼭 봐야겠네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://icksw.tistory.com/266&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Meet async / await in Swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10254&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift Concurrency: Behind the scenes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 비동기 작업은 어떻게 하는지 알아봤으니 다음은 async / await를 사용하는 Swift의 새로운 동시성 지원에 대해 알아보도록 하겠습니다.&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-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NSBf2/btrfFEXj7eu/r3lfp0eriunPizqdRusmg0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NSBf2/btrfFEXj7eu/r3lfp0eriunPizqdRusmg0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NSBf2/btrfFEXj7eu/r3lfp0eriunPizqdRusmg0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/NSBf2/btrfFEXj7eu/r3lfp0eriunPizqdRusmg0/img.gif&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 3개의 다른 이미지를 렌더링 한 뒤 merge 하는 함수입니다. 위와 같이 순차적으로 진행되는 코드죠. 이렇게 진행되면 background, foreground, title이 순차적으로 그려지게 되는데, 그거보다는 3개의 이미지가 동시에 그려지는 게 좀 더 좋겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF6oVq/btrfPB5OUY2/GTBQpNW7tmWiXXxpfNVcL1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF6oVq/btrfPB5OUY2/GTBQpNW7tmWiXXxpfNVcL1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF6oVq/btrfPB5OUY2/GTBQpNW7tmWiXXxpfNVcL1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cF6oVq/btrfPB5OUY2/GTBQpNW7tmWiXXxpfNVcL1/img.gif&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 그려지더라도 3개의 렌더링 작업이 완료될 때까지 merge 작업은 실행되면 안 됩니다. 따라서 아래와 같이 코드를 수정해줘야 합니다.&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-origin-width=&quot;1234&quot; data-origin-height=&quot;744&quot; width=&quot;742&quot; height=&quot;448&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy50dl/btrfQnsusdJ/Rf0apnLODYkVLo4FAEGvH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy50dl/btrfQnsusdJ/Rf0apnLODYkVLo4FAEGvH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy50dl/btrfQnsusdJ/Rf0apnLODYkVLo4FAEGvH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy50dl%2FbtrfQnsusdJ%2FRf0apnLODYkVLo4FAEGvH1%2Fimg.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;744&quot; width=&quot;742&quot; height=&quot;448&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 선언부에 async 키워드를 작성하고 지역변수들인 background, foreground를 async let으로 선언하면 해당 값들이 준비될 때까지 merge 작업을 기다립니다. 이를 나타내기 위해서 merge 앞에 await 키워드도 작성해주면 됩니다. 그리고 둘 중 하나의 작업이라도 완료되지 않았다면 함수가 반환되지 않기 때문에 혹시라도 오류가 있으면 반환하기 위한 throws, try 오류처리도 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에 대한 좀 더 자세한 내용은 아래 영상을 참고하면 된다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10134&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Explore structured concurrency in Swift&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Actor&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Swift 5.5에서 여러 스레드에서 작업을 쉽게 실행하는지 봤는데요, 멀티 스레드 환경에서 자주 발생하는 문제 중 하나인 공유자원을 처리하는 문제는 async, await 만으로 해결할 수 없습니다. 이를 해결하기 위해서는 Swift의 새로운 Actor를 사용해야 합니다. 이번에도 예를 볼게요!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;173&quot; width=&quot;408&quot; height=&quot;242&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf9RXL/btrfRDIBgvj/W4CzklKsUAf52OwXJcUFOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf9RXL/btrfRDIBgvj/W4CzklKsUAf52OwXJcUFOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf9RXL/btrfRDIBgvj/W4CzklKsUAf52OwXJcUFOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf9RXL%2FbtrfRDIBgvj%2FW4CzklKsUAf52OwXJcUFOK%2Fimg.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;173&quot; width=&quot;408&quot; height=&quot;242&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 통계를 수집하는 클래스입니다. 클래스는 참조 타입이므로 힙 공간에 존재해서 여러 개의 스레드가 공유 자원으로 사용하게 됩니다. 이 클래스에는 counter가 있고 increment 메서드로 counter의 값을 1씩 증가시킬 수 있죠. 근데 이 코드는 멀티 스레드에서는 잘 작동하지 않습니다. 공유자원에 대한 보호가 없다면 말이죠.&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-origin-width=&quot;592&quot; data-origin-height=&quot;352&quot; width=&quot;508&quot; height=&quot;302&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8hN5P/btrfO48gbUv/qweXHcFZAXKsN4l9X0fRBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8hN5P/btrfO48gbUv/qweXHcFZAXKsN4l9X0fRBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8hN5P/btrfO48gbUv/qweXHcFZAXKsN4l9X0fRBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8hN5P%2FbtrfO48gbUv%2FqweXHcFZAXKsN4l9X0fRBk%2Fimg.png&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;352&quot; width=&quot;508&quot; height=&quot;302&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서 Class 대신 Swift Actor를 사용하게 됩니다. Actor는 이상한 데이터를 만들 수 있는 작업이 안전해질 때까지 작업을 일시 중단하며 작동합니다. 따라서 Actor 외부에서 Actor 메서드를 호출할 때 await를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;352&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2iZjZ/btrfHcMzLVN/PKZ36kHCMophUKK5WhTFZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2iZjZ/btrfHcMzLVN/PKZ36kHCMophUKK5WhTFZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2iZjZ/btrfHcMzLVN/PKZ36kHCMophUKK5WhTFZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2iZjZ%2FbtrfHcMzLVN%2FPKZ36kHCMophUKK5WhTFZ0%2Fimg.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;352&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 위와 같이 async / await에서도 잘 작동하는데, 위와 같이 publish() 메서드를 async로 선언하면 네트워크 작업을 수행하는 동안 해당 메서드가 멈추게 됩니다. 일시 중지되는 동안에는 네트워크 작업이 완료될 때까지 기다리지 않고 데이터 손상 위험 없이 Actor의 다른 메서드를 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Actor는 Class와 같은 참조 타입이지만 멀티 스레드 환경에서 안전하게 사용할 수 있도록 해줍니다. 즉 Actor로 선언한 데이터는 여러 스레드가 동시에 접근할 수 있음을 명시하는 것이고, 런타임 접근을 제한하여 데이터가 스레드로부터 안전하도록 하는 것입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor에 대한 자세한 내용은 아래 영상을 참고하라고 합니다. 재밌어 보이니 이거도 나중에 봐야겠어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10133&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Protect mutable state with Swift actors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 이후 영상에서는 Swift 6을 개선하기 위해서는 사용자의 경험이 중요하다면서 커뮤니티에 많은 활동을 부탁한다 정도의 내용이라 따로 정리하지는 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 Swift 5.5는 비동기, 동시성 프로그래밍 작업 시 많은 도움이 될 것 같습니다. 게다가 성능도 좋아졌다고 하니 아주 기대가 됩니다.  이번 영상은 Swift 5.5의 변경사항들을 간단하게 보는 영상이라 자세한 건 언급된 영상들을 봐야지 자세히 알 수 있을 거 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘 WWDC 영상인 &quot;What's new in Swift&quot;는 여기까지 정리하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다. &lt;/p&gt;</description>
      <category>Apple/WWDC 2021</category>
      <category>2021</category>
      <category>5.5</category>
      <category>Apple</category>
      <category>development</category>
      <category>IOS</category>
      <category>MacOS</category>
      <category>Swift</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/265</guid>
      <comments>https://icksw.tistory.com/265#entry265comment</comments>
      <pubDate>Fri, 24 Sep 2021 00:47:47 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC 2021] What's new in UIKit</title>
      <link>https://icksw.tistory.com/264</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC 2021이 열린지도 꽤 많은 시간이 지났고, 몇 개 보긴 했는데.. 정리를 안 하니까 뭔가 제 지식이 안 되는 느낌이라서 이번 주부터 꾸준히 WWDC 영상을 정리하는 글을 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 정리할 영상은 &quot;What's new in UIKit&quot; 이라는 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10059/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;영상&lt;/a&gt;으로 iOS 15에서 새롭게 추가된 UIKit의 여러 가지 기능들을 소개해주는 영상이었습니다. 간단하게 추가된 기능들을 나열하는 형태로 진행됐고, 세부적인 내용들은 따로 세션이 존재해서 관심 있는 부분들을 보면 좋을 것 같습니다.&lt;/p&gt;
&lt;h1&gt;What's new in UIKit&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서 UIKit에서 새롭게 추가된 기능들을 정리하면 아래와 같았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Productivity&lt;/li&gt;
&lt;li&gt;UI refinements&lt;/li&gt;
&lt;li&gt;API enhancements&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Security and privacy&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 순서대로 새로운 기능들을 나열해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Productivity (생산성)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생산성은 iPadOS의 핵심이라고 할 수 있는데요! 아래와 같은 기능이 추가되었다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;multitasking&lt;/li&gt;
&lt;li&gt;iPad pointer&lt;/li&gt;
&lt;li&gt;keyboard shortcuts&lt;/li&gt;
&lt;li&gt;keyboard navigation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;multitasking&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit1.gif&quot; width=&quot;473&quot; height=&quot;292&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbNV54/btrd2x5DGHy/lLuvodei0eYwWKQ5kPswDK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbNV54/btrd2x5DGHy/lLuvodei0eYwWKQ5kPswDK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbNV54/btrd2x5DGHy/lLuvodei0eYwWKQ5kPswDK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bbNV54/btrd2x5DGHy/lLuvodei0eYwWKQ5kPswDK/img.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit1.gif&quot; width=&quot;473&quot; height=&quot;292&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 메일에서 long press 제스처를 활용해서 context menu를 열어 다양한 작업을 할 수도 있고 영상에서 볼 수 있듯이 독립적인 UIWindowScene을 사용 할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit2.gif&quot; width=&quot;471&quot; height=&quot;291&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctHtpT/btrd044b8P7/Ruhj9Htfcylbbrc2RzFDXK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctHtpT/btrd044b8P7/Ruhj9Htfcylbbrc2RzFDXK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctHtpT/btrd044b8P7/Ruhj9Htfcylbbrc2RzFDXK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ctHtpT/btrd044b8P7/Ruhj9Htfcylbbrc2RzFDXK/img.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit2.gif&quot; width=&quot;471&quot; height=&quot;291&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이렇게 만들어진 화면은 window 상단에 존재하는 멀티태스킹 메뉴에서 split view, drag and drop과 같은 기능을 사용할 수도 있습니다. 그리고 멀티태스킹 메뉴에서 아래로 스와이프 해서 window shelf에 scene을 고정할 수도 있답니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;384&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/316zw/btrd04pBHOi/Km4QLv3iyjOrhbXologee0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/316zw/btrd04pBHOi/Km4QLv3iyjOrhbXologee0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/316zw/btrd04pBHOi/Km4QLv3iyjOrhbXologee0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F316zw%2Fbtrd04pBHOi%2FKm4QLv3iyjOrhbXologee0%2Fimg.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;384&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 기능을 추가하는건 매우 간단한데요, 위와 같이 UIWindowScene의 ActivationAction으로 만들어 줄 수 있습니다. 이렇게 만든 액션을 context menu에 추가하면 바로 사용할 수 있게 됩니다.&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;iPad Pointer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 iPadOS 13.4에서 추가된 Magic Keyboard, pointer에 대한 내용인데요,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVkm2M/btrd15au4xC/1sO0cLoPxs70Qm1xEzkZHk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVkm2M/btrd15au4xC/1sO0cLoPxs70Qm1xEzkZHk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVkm2M/btrd15au4xC/1sO0cLoPxs70Qm1xEzkZHk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bVkm2M/btrd15au4xC/1sO0cLoPxs70Qm1xEzkZHk/img.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위와 같이 컬렉션뷰에서 여러 개를 선택하는 band selection이라는 기능이 추가되었다고 합니다. 엄청 편해 보이긴 합니다.  &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-origin-width=&quot;1134&quot; data-origin-height=&quot;488&quot; width=&quot;611&quot; height=&quot;263&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7fYMm/btrd649sX7W/16tbcl6gBifBUEcjBmKBMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7fYMm/btrd649sX7W/16tbcl6gBifBUEcjBmKBMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7fYMm/btrd649sX7W/16tbcl6gBifBUEcjBmKBMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7fYMm%2Fbtrd649sX7W%2F16tbcl6gBifBUEcjBmKBMk%2Fimg.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;488&quot; width=&quot;611&quot; height=&quot;263&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 pointer의 기능을 전달 할 수 있는 위와 같은 pointer accessories들이 추가되었다고 하네요!&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;keyboard shorcuts&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;614&quot; width=&quot;678&quot; height=&quot;515&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lGSkG/btrd2xYSt1Y/wP7KqqdDSayM2zOKXlTbqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lGSkG/btrd2xYSt1Y/wP7KqqdDSayM2zOKXlTbqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lGSkG/btrd2xYSt1Y/wP7KqqdDSayM2zOKXlTbqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlGSkG%2Fbtrd2xYSt1Y%2FwP7KqqdDSayM2zOKXlTbqK%2Fimg.png&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;614&quot; width=&quot;678&quot; height=&quot;515&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iPadOS 15에는 keyboard shortcut menu가 추가되었습니다. 위와 같이 다양한 기능을 쉽게 사용할 수 있게 해 줍니다.&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-origin-width=&quot;1196&quot; data-origin-height=&quot;572&quot; width=&quot;739&quot; height=&quot;353&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBihAz/btrd651CRsl/6qte7jwZor3veOP6JmE6u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBihAz/btrd651CRsl/6qte7jwZor3veOP6JmE6u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBihAz/btrd651CRsl/6qte7jwZor3veOP6JmE6u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBihAz%2Fbtrd651CRsl%2F6qte7jwZor3veOP6JmE6u1%2Fimg.png&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;572&quot; width=&quot;739&quot; height=&quot;353&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기능은 위와 같이 UIMenuBuilder로 만들 수 있습니다. 기본적으로 제공되는 category도 있고 개발자가 직접 만들 수도 있다고 합니다. 직접 카테고리를 만들기 위해서는 UIResponder의 keyCommands 프로퍼티를 사용하면 된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분에 대해 더 알고 싶다면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10057&quot;&gt;&lt;span&gt;Take your iPad apps to the next level&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 영상을 참고하라고 하네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;keyboard navigation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;iPadOS 15에서는 포커스 기반 keyboard navigation에 대한 지원을 추가했다고 합니다. 저는 tvOS의 focus system을 사용해보지 않았는데, 그거랑 동일한 기능을 iPadOS에서도 사용할 수 있게 되었다고 합니다. UIFocusSystem으로 tvOS, CarPlay, Mac, iPadOS에서 동일하게 사용할 수 있다고 합니다.&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&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit4.gif&quot; width=&quot;488&quot; height=&quot;301&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XWZ0A/btrd5kxXAuw/BzTkeHwpRIYGh5p8fGCol0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XWZ0A/btrd5kxXAuw/BzTkeHwpRIYGh5p8fGCol0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XWZ0A/btrd5kxXAuw/BzTkeHwpRIYGh5p8fGCol0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/XWZ0A/btrd5kxXAuw/BzTkeHwpRIYGh5p8fGCol0/img.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit4.gif&quot; width=&quot;488&quot; height=&quot;301&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 키보드의 화살표 키를 사용해서 포커스 항목 간에 이동을 하고 tap 키를 사용해서 포커스 그룹을 이동할 수 있다고 합니다. 위의 메일 앱의 뷰를 위해서는 UISplitViewController를 사용한다고 합니다. 또한 추가된 기능은 UIKit의 컴포넌트들에서는 iPadOS 15부터 기본적으로 활성화된 상태로 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 대한 더 많은 정보는 아래 영상을 참고하라고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10260&quot;&gt;Focus on iPad keyboard navigation&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;multi-touch, Drag and Drop&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서 생산성을 위해서 개선된 내용은 multi-touch, Drag and Drop입니다. iOS 11부터 도입된 drag and drop 기능은 생산성을 많이 증가시켜줬는데요, iPadOS에서는 간단한 제스처로 여러 응용 프로그램에서 데이터를 이동할 수 있었습니다.&lt;/p&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 15에서도 사용할 수 있다고 해요!  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit5.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0vnXD/btrd15PbVlH/VqxRER13RA59Jj8BEbvc20/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0vnXD/btrd15PbVlH/VqxRER13RA59Jj8BEbvc20/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0vnXD/btrd15PbVlH/VqxRER13RA59Jj8BEbvc20/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/0vnXD/btrd15PbVlH/VqxRER13RA59Jj8BEbvc20/img.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit5.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 간단하게 다른 앱에서 데이터를 이동할 수 있다고 합니다. 이 기능은 iPad에서 정말 유용하게 사용하고 있었는데, 이제 iPhone에서도 된다니 편하겠네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능을 더 알아보고 싶으면 아래 영상들을 참고하면 된다고 합니다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/227&quot;&gt;&lt;span&gt;Data Delivery with Drag and Drop&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/223&quot;&gt;Drag and Drop with Collection and Table View&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/203&quot;&gt;Introducing Drag and Drop&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2017/213&quot;&gt;Mastering Drag and Drop&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;UI refinements (UI 개선)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 UI refinements에 대해 알아보겠습니다.&lt;/p&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 15부터 몇 가지 UI가 달라졌다고 하는데요, 먼저 UIToolbar, UITabBar입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;506&quot; width=&quot;723&quot; height=&quot;381&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZxVP2/btrd3b2mAiG/gJJ2moZiKFjQxNqucqOEKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZxVP2/btrd3b2mAiG/gJJ2moZiKFjQxNqucqOEKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZxVP2/btrd3b2mAiG/gJJ2moZiKFjQxNqucqOEKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZxVP2%2Fbtrd3b2mAiG%2FgJJ2moZiKFjQxNqucqOEKK%2Fimg.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;506&quot; width=&quot;723&quot; height=&quot;381&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 업데이트는 맨 아래로 스크롤할 때 background material을 제거해서 시각적으로 좀 더 명확하게 보여주려고 한대요.&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-origin-width=&quot;1146&quot; data-origin-height=&quot;428&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YSK0m/btrd2xEBi7U/GVnyQIT5BLxMdgt6Y0eykk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YSK0m/btrd2xEBi7U/GVnyQIT5BLxMdgt6Y0eykk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YSK0m/btrd2xEBi7U/GVnyQIT5BLxMdgt6Y0eykk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYSK0m%2Fbtrd2xEBi7U%2FGVnyQIT5BLxMdgt6Y0eykk%2Fimg.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;428&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit은 앱에서 이러한 모양을 만들 때 몇 가지 문제가 생길 수 있다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bar의 translucent(반투명) 프로퍼티를 false로 UIViewController의 edgesForExtenedLayout가 non-standard 값을 갖는지 확인해야 한다고 합니다. 위와 같이 만들면 새로운 모양이 잘 안 나온다고 하네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;632&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EHvO6/btrd5jFPsRu/ToTE0YxxnfUZ3CNwiNfmT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EHvO6/btrd5jFPsRu/ToTE0YxxnfUZ3CNwiNfmT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EHvO6/btrd5jFPsRu/ToTE0YxxnfUZ3CNwiNfmT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEHvO6%2Fbtrd5jFPsRu%2FToTE0YxxnfUZ3CNwiNfmT0%2Fimg.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;632&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위와 같이 직접 appearance를 만들고 scrollEdgeApperance에 할당해주면 된다고 합니다. 해당 기능은 원래 UINavigationBar에서만 사용 가능했는데 이젠 UIToolbar, UITabBar에서도 사용할 수 있게 되었다고 합니다. 이렇게 직접 설정하게 되면 아까 코드에서 발생할 수 있었던 시각적 오류를 피할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 새로운 scrollEdgeApperance를 수행할 때 UIKit이 관찰하기 위한 적절한 스크롤 뷰를 찾지 못할 수도 있다고 합니다. 그래서 scrollView를 직접 지정해서 이런 문제를 해결하기 위해 UIViewController에 setContentScrollView 메서드를 추가했다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 일단 사용을 직접 해 봐야 정확하게 알 수 있을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 Header에도 변경 부분이 있는데요,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;706&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx195E/btrd0FKlQNL/jskcQTkmYqfH5jVAQnI1KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx195E/btrd0FKlQNL/jskcQTkmYqfH5jVAQnI1KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx195E/btrd0FKlQNL/jskcQTkmYqfH5jVAQnI1KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx195E%2Fbtrd0FKlQNL%2FjskcQTkmYqfH5jVAQnI1KK%2Fimg.png&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;706&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 제공하던 타입에는 .plain, .grouped 타입이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.plain 타입의 경우 섹션 헤더가 콘텐츠와 함께 잘 표시되고 아래로 스크롤할 때 맨 위에 고정될 때 보이는 background material만 표시된다고 합니다. 그리고 각 섹션 헤더 위에 새로운 패딩이 삽입돼서 새로운 디자인으로 섹션을 구분한다고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;.grouped 타입은 위의 사진과 같이 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;704&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDr0G8/btrd9lb4o8z/YKtQqykL80Kv9K5V28k1i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDr0G8/btrd9lb4o8z/YKtQqykL80Kv9K5V28k1i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDr0G8/btrd9lb4o8z/YKtQqykL80Kv9K5V28k1i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDr0G8%2Fbtrd9lb4o8z%2FYKtQqykL80Kv9K5V28k1i1%2Fimg.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;704&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 추가된 헤더는 .prominentInsetGrouped 타입, .extraProminentInsetGrouped 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.prominentInsetGrouped 타입의 헤더는 기존 sidebar의 헤더와 비슷하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.extraProminentInsetGrouped 타입은 헤더가 계층 구조를 유지하고 시각적으로 풍부한 콘텐츠와 함께 사용할 수 있게 해주는 타입입니다.&lt;/p&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 14에 추가된 UIListContentConfiguration API를 사용하면 된다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;506&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3V0Fj/btrd65AyEIZ/WAxECf8sgoJ5xkeceBNSEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3V0Fj/btrd65AyEIZ/WAxECf8sgoJ5xkeceBNSEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3V0Fj/btrd65AyEIZ/WAxECf8sgoJ5xkeceBNSEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3V0Fj%2Fbtrd65AyEIZ%2FWAxECf8sgoJ5xkeceBNSEk%2Fimg.png&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;506&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 14.5에서는 리스트의 구분 기호를 완전히 제어할 수 있는 UIListSeparatorConfiguration이 추가되었습니다. 전체 리스트에 대한 구성을 지정하거나 시스템 생성 모양을 오버라이드 해서 구분 기호를 제어할 수 있게 되었다고 합니다.&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-origin-width=&quot;580&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit7.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9VA3v/btrd03xtjAD/LRbhYKpTlCh0bt5XPzi3s0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9VA3v/btrd03xtjAD/LRbhYKpTlCh0bt5XPzi3s0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9VA3v/btrd03xtjAD/LRbhYKpTlCh0bt5XPzi3s0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b9VA3v/btrd03xtjAD/LRbhYKpTlCh0bt5XPzi3s0/img.gif&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit7.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15의 Sheet에는 위와 같이 화면의 절반만 덮는 기능이 추가되었습니다. 이렇게 하면 두 개의 화면을 동시에 활용할 수 있어 편리할 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능에 대해 더 알고 싶다면 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10063/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Customize and resize sheets in UIKit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit8.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bio9P/btrd2xkkXTe/FbYhsQg8OeYfHi97ArOVf0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bio9P/btrd2xkkXTe/FbYhsQg8OeYfHi97ArOVf0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bio9P/btrd2xkkXTe/FbYhsQg8OeYfHi97ArOVf0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Bio9P/btrd2xkkXTe/FbYhsQg8OeYfHi97ArOVf0/img.gif&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit8.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 UIDatePicker입니다. iOS 15에서는 wheel로만 날짜나 시간을 변경하는 것이 아닌 키보드로 직접 입력이 가능하도록 개선되었다고 해요. 오.. 편하긴 하겠네요.&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-origin-width=&quot;580&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit9.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E1Lqj/btrd0XcFhJ0/WniddBRcWx6MkOnAoODkRK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E1Lqj/btrd0XcFhJ0/WniddBRcWx6MkOnAoODkRK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E1Lqj/btrd0XcFhJ0/WniddBRcWx6MkOnAoODkRK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/E1Lqj/btrd0XcFhJ0/WniddBRcWx6MkOnAoODkRK/img.gif&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit9.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iPad에서는 매직 키보드를 사용해서도 해당 기능을 사용할 수 있다고 합니다. 물론 기존의 wheel도 쓸 수 있다고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;API Enhancement (API 개선)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 iOS 15에서 개선된 UIKit API들을 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;514&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtkEt/btrd5ljnklc/RcTNTYLKZc5qASmHyoajPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtkEt/btrd5ljnklc/RcTNTYLKZc5qASmHyoajPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtkEt/btrd5ljnklc/RcTNTYLKZc5qASmHyoajPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmtkEt%2Fbtrd5ljnklc%2FRcTNTYLKZc5qASmHyoajPk%2Fimg.png&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;514&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 UIButton의 모양과 느낌을 자유롭게 만들 수 있도록 새로운 API를 추가했다고 합니다. 위와 같이 기존 버튼 스타일과 배경색이 있는 Tinted, 그리고 불투명한 Filled 타입이 추가되었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기에 추가로 버튼 텍스트에 여러 줄이 이제 가능하다고 합니다! 오! 언젠가 텍스트가 2줄짜리 버튼을 만들기 위해서 UILabel + Button의 조합으로 버튼을 만든 적이 있었는데.. 이제 그럴 필요는 없겠네요.&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-origin-width=&quot;294&quot; data-origin-height=&quot;320&quot; data-filename=&quot;UIKit10.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8fzrU/btrea0ZOh9v/UVz5mREKHXSTtF6AFeLzi1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8fzrU/btrea0ZOh9v/UVz5mREKHXSTtF6AFeLzi1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8fzrU/btrea0ZOh9v/UVz5mREKHXSTtF6AFeLzi1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b8fzrU/btrea0ZOh9v/UVz5mREKHXSTtF6AFeLzi1/img.gif&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;320&quot; data-filename=&quot;UIKit10.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼의 모양 말고도 iOS 14에서 추가된 버튼에 UIMenu를 사용하는 방법과 UIButtonConfiguration을 사용하면 UIKit에서 pop up, pull down 버튼을 만들 수도 있다고 합니다. 이렇게 추가한 기능을 Mac에서 사용하려면 Xcode의 &quot;Optimize Interface For Mac&quot;을 사용하면 된다고 합니다.&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-origin-width=&quot;1490&quot; data-origin-height=&quot;862&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yeVVo/btrd2wTc6XU/KRETQ9BYBX84sijurx0mCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yeVVo/btrd2wTc6XU/KRETQ9BYBX84sijurx0mCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yeVVo/btrd2wTc6XU/KRETQ9BYBX84sijurx0mCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyeVVo%2Fbtrd2wTc6XU%2FKRETQ9BYBX84sijurx0mCK%2Fimg.png&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;862&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10064&quot;&gt;Meet the UIKit button system&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS, iPadOS의 UIContextMenuInteraction은 이제 collapsible submenus를 가질 수 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit11.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nf24w/btrd04XvLc8/ytFs8gOhQtmktkDXa31LEK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nf24w/btrd04XvLc8/ytFs8gOhQtmktkDXa31LEK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nf24w/btrd04XvLc8/ytFs8gOhQtmktkDXa31LEK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Nf24w/btrd04XvLc8/ytFs8gOhQtmktkDXa31LEK/img.gif&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;348&quot; data-filename=&quot;UIKit11.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 사용할 수 있다고 하는데요, 사실 UIMenu API의 경우 해당 기능을 원래 제공했지만, 서브메뉴를 선택하면 해당 메뉴로 현재 메뉴가 초기화되었는데, iOS 15부터는 그렇게 되지 않는다고 합니다. 해당 부분도 아래 영상에서 자세히 다룬다고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10064&quot;&gt;Meet the UIKit button system&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;636&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7dB9y/btrd658r0FN/lR4DgojNrqWG1U6vkZUj6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7dB9y/btrd658r0FN/lR4DgojNrqWG1U6vkZUj6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7dB9y/btrd658r0FN/lR4DgojNrqWG1U6vkZUj6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7dB9y%2Fbtrd658r0FN%2FlR4DgojNrqWG1U6vkZUj6K%2Fimg.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;636&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 위와 같이 SF Symbol이 예뻐졌답니다. 여러 가지 색을 조합할 수도 있게 되었네요.&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-origin-width=&quot;1282&quot; data-origin-height=&quot;762&quot; width=&quot;762&quot; height=&quot;453&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xjipk/btrd1x6bdbg/Apnu7z1uP1bnRB7kRG2w01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xjipk/btrd1x6bdbg/Apnu7z1uP1bnRB7kRG2w01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xjipk/btrd1x6bdbg/Apnu7z1uP1bnRB7kRG2w01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxjipk%2Fbtrd1x6bdbg%2FApnu7z1uP1bnRB7kRG2w01%2Fimg.png&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;762&quot; width=&quot;762&quot; height=&quot;453&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용 예를 보면 간단합니다. UIImage.SymbolConfiguration을 설정해서 원하는 색의 조합으로 만드는 것을 볼 수 있어요.&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-origin-width=&quot;1138&quot; data-origin-height=&quot;634&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/babOAA/btrd5krcYVu/fYTDKq0OdDSeHcwO9TXsRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/babOAA/btrd5krcYVu/fYTDKq0OdDSeHcwO9TXsRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/babOAA/btrd5krcYVu/fYTDKq0OdDSeHcwO9TXsRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbabOAA%2Fbtrd5krcYVu%2FfYTDKq0OdDSeHcwO9TXsRk%2Fimg.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;634&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에는 UIImage API에 간단하게 SF Symbol 이미지를 변형하는 기능도 추가되었다고 해요. 해당 내용은 아래 영상에서 자세히 다룬다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://developer.apple.com/videos/play/wwdc2021/10251&quot;&gt;SF Symbols in UIKit and AppKit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit12.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bja9Ah/btrd5kELtvT/w4XpDYYE8GhISefEG5xI0k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bja9Ah/btrd5kELtvT/w4XpDYYE8GhISefEG5xI0k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bja9Ah/btrd5kELtvT/w4XpDYYE8GhISefEG5xI0k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bja9Ah/btrd5kELtvT/w4XpDYYE8GhISefEG5xI0k/img.gif&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit12.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 뷰 계층 구조에 trait가 적용되는 방식을 제한하는 새로운 방법을 추가했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIContentSizeCategory 특성은 코드에서 dynamic type size라고도 하는 system text의 크기 설정을 나타내는 특성입니다. Label, textField, textView, imageView를 설정해서 자동으로 해당 특성을 조정할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 앱의 텍스트와 이미지가 모든 크기에서 잘 보이도록 만들 때 도움이 된다고 합니다. 잘 보면 시계 앱의 텍스트 크기를 쉽게 바꾸는 것을 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;632&quot; width=&quot;556&quot; height=&quot;502&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg263a/btrd65tOvxv/RoaFZRIf0AE6euh5sJSO4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg263a/btrd65tOvxv/RoaFZRIf0AE6euh5sJSO4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg263a/btrd65tOvxv/RoaFZRIf0AE6euh5sJSO4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg263a%2Fbtrd65tOvxv%2FRoaFZRIf0AE6euh5sJSO4k%2Fimg.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;632&quot; width=&quot;556&quot; height=&quot;502&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 모든 플랫폼에서 system color를 통일했다고 합니다. 이에 따라 일부 플랫폼에서만 사용할 수 있었던 색들을 이젠 모든 곳에서 사용할 수 있게 됐다고 하네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;478&quot; width=&quot;457&quot; height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dInyr2/btrea2QRVVK/x5D30vHj15kdO5BPW7gh6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dInyr2/btrea2QRVVK/x5D30vHj15kdO5BPW7gh6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dInyr2/btrea2QRVVK/x5D30vHj15kdO5BPW7gh6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdInyr2%2Fbtrea2QRVVK%2Fx5D30vHj15kdO5BPW7gh6k%2Fimg.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;478&quot; width=&quot;457&quot; height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 UIColor입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIColor의 Tint color는 앱은 런타임에서 색을 확인하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음 근데 뭐가 추가되었다고 하는데 뭔진 잘 모르겠네요;;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit13.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blBoHO/btrd2ycviGX/ZCoPqsfru6mZUXjqYQGCn1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blBoHO/btrd2ycviGX/ZCoPqsfru6mZUXjqYQGCn1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blBoHO/btrd2ycviGX/ZCoPqsfru6mZUXjqYQGCn1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/blBoHO/btrd2ycviGX/ZCoPqsfru6mZUXjqYQGCn1/img.gif&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit13.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIColorPickerViewController에도 새로운 기능이 추가됐다고 합니다. 새로운 콜백 함수인 색상이 선택됐을 때와 선택이 완료됐을 때 UI를 업데이트할 수 있게 되었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;684&quot; width=&quot;643&quot; height=&quot;402&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNof9g/btrd9lb6nsZ/ryDSkVfYJnTYbA6nLb28q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNof9g/btrd9lb6nsZ/ryDSkVfYJnTYbA6nLb28q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNof9g/btrd9lb6nsZ/ryDSkVfYJnTYbA6nLb28q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNof9g%2Fbtrd9lb6nsZ%2FryDSkVfYJnTYbA6nLb28q1%2Fimg.png&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;684&quot; width=&quot;643&quot; height=&quot;402&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextKit 2는 iOS, iPadOS, tvOS, macOS에서 사용할 수 있는 새로운 텍스트 레이아웃 시스템이라고 합니다. UIKit에서는 이를 사용하기 위해서 UITextField를 수정했다고 합니다. 이에 대한 자세한 내용은 아래 영상을 참고하라고 하네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10061&quot;&gt;Meet TextKit 2&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UISceneSession은 앱 UI의 인스턴스를 나타내고 앱 전환기에 표시되는 앱 창에 해당합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;446&quot; width=&quot;609&quot; height=&quot;377&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eh7xGv/btrd7SAWlQK/P5FhdQdTTZtdtSYsE5KbIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eh7xGv/btrd7SAWlQK/P5FhdQdTTZtdtSYsE5KbIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eh7xGv/btrd7SAWlQK/P5FhdQdTTZtdtSYsE5KbIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feh7xGv%2Fbtrd7SAWlQK%2FP5FhdQdTTZtdtSYsE5KbIk%2Fimg.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;446&quot; width=&quot;609&quot; height=&quot;377&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 상태는 NSUserActivity로 표시되는데, 앱은 Scene이 background 상태가 될 때 NSUserActivity를 시스템에 제공하고 Scene이 다시 인스턴스화 될 때 인터페이스 상태를 복원하기 위해 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iPadOS 15에는 이를 좀 더 간단하게 하기 위해 새로운 API가 추가되었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;text input view의 상태를 가지고 오고 설정하는 방법과 새로운 UIScene 콜백, 상태를 반환하는 비동기 모델 코드가 있는 경우 앱 실행 프로세스를 확장하고 앱의 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;따라서 iPadOS 13 이전에 사용되던 UIApplication 기반 life cycle을 아직 사용 중이라면 이번 기회에 UIScene으로 바꿔보라고 하네요 ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용에 대한 더 많은 내용은 아래 영상에서 확인할 수 있다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10057&quot;&gt;Take your iPad apps to the next level&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;476&quot; width=&quot;560&quot; height=&quot;370&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbFzDU/btrd2lcQQVa/rbFkkYHZqWwi23GEuKxhy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbFzDU/btrd2lcQQVa/rbFkkYHZqWwi23GEuKxhy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbFzDU/btrd2lcQQVa/rbFkkYHZqWwi23GEuKxhy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbFzDU%2Fbtrd2lcQQVa%2FrbFkkYHZqWwi23GEuKxhy1%2Fimg.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;476&quot; width=&quot;560&quot; height=&quot;370&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15의 UIKit에는 앱이 각각의 Scene에서 상호 작용하는 공유 가능한 콘텐츠를 나타낼 수 있도록 하는 새로운 API가 추가되었다고 합니다. 이게 어서 사용해보고 싶은 페이스타임을 사용하면서 영상 공유하는 등의 내용 같아요. 더 자세한 내용은 아래 영상에서 다룬다고 합니다. 꼭 봐야겠어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10283&quot;&gt;Design great actions for Shortcuts, Siri, and Suggestions&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10053&quot;&gt;Qualities of a great Mac Catalyst app&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;458&quot; width=&quot;585&quot; height=&quot;371&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BoiXM/btrd3aCqCVb/Jy6dwwzk2xzYdHdImXwFt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BoiXM/btrd3aCqCVb/Jy6dwwzk2xzYdHdImXwFt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BoiXM/btrd3aCqCVb/Jy6dwwzk2xzYdHdImXwFt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBoiXM%2Fbtrd3aCqCVb%2FJy6dwwzk2xzYdHdImXwFt0%2Fimg.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;458&quot; width=&quot;585&quot; height=&quot;371&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 UICollectionView, UITableView의 개선사항입니다. iOS 15부터는 새로운 클로저 기반 업데이트 핸들러를 추가해서 셀을 쉽게 재구성할 수 있다고 합니다.&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-origin-width=&quot;764&quot; data-origin-height=&quot;450&quot; width=&quot;530&quot; height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WuWvL/btrd14pcHAd/zG17IXhjRz2dU9hICkwoR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WuWvL/btrd14pcHAd/zG17IXhjRz2dU9hICkwoR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WuWvL/btrd14pcHAd/zG17IXhjRz2dU9hICkwoR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWuWvL%2Fbtrd14pcHAd%2FzG17IXhjRz2dU9hICkwoR1%2Fimg.png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;450&quot; width=&quot;530&quot; height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 뷰와 테이블 뷰를 더 쉽게 업데이트할 수 있도록 diffable data source를 개선했다고 하네요. iOS 15에서 애니메이션의 차이 없이 스냅샷을 적용하면 기존 셀들을 지우지 않고 변경 사항에 맞게 UI가 업데이트된다고 합니다. 또한 item들을 효율적으로 재구성하는 새로운 API가 있어서 item의 프로퍼티가 변경될 때 ID를 변경하지 않고 기존 셀에 표시되는 콘텐츠를 업데이트할 수 있다고 합니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Performance (성능)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 빠르게 작업을 처리하고 애니메이션과 스크롤이 부드럽게 처리되도록 하는 UIKit의 성능 향상도 이뤘다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;370&quot; width=&quot;534&quot; height=&quot;300&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqraVq/btrd5jMFGSY/ZweWLkMAVRs80vvS8TwKxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqraVq/btrd5jMFGSY/ZweWLkMAVRs80vvS8TwKxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqraVq/btrd5jMFGSY/ZweWLkMAVRs80vvS8TwKxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqraVq%2Fbtrd5jMFGSY%2FZweWLkMAVRs80vvS8TwKxK%2Fimg.png&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;370&quot; width=&quot;534&quot; height=&quot;300&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 UICollectionView, UITableView의 cell prefetching이 변경되었다고 합니다. 변경 사항은 iOS 15용으로 빌드할 때 알아서 된다고 하네요.. 오.. 따로 작업할 게 없다는 말인 듯합니다. 해당 변경으로 인해 앱에서 최대 2배 정도의 성능 향상이 이뤄졌다고 합니다.&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-origin-width=&quot;824&quot; data-origin-height=&quot;344&quot; width=&quot;755&quot; height=&quot;315&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biYzyp/btrea1R1GUo/Hkg2sp5xi2oTjadKUMnom1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biYzyp/btrea1R1GUo/Hkg2sp5xi2oTjadKUMnom1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biYzyp/btrea1R1GUo/Hkg2sp5xi2oTjadKUMnom1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiYzyp%2Fbtrea1R1GUo%2FHkg2sp5xi2oTjadKUMnom1%2Fimg.png&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;344&quot; width=&quot;755&quot; height=&quot;315&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 예를 보면, 셀에서 이미지를 보여주는 경우가 많은데요, 이전에는 UI Queue가 큰 이미지를 디코딩할 때 스크롤이 일시적으로 멈추는 현상이 존재했었습니다. 그래서 이를 해결하기 위해 iOS 15부터는 앱 코드에서 이러한 부분을 제어할 수 있도록 개선되었다고 합니다. 이러한 기능은 Swift 5.5에서 추가된 async, await를 사용해서 잘 처리할 수 있게 되었다고 하네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분에 대한 자세한 내용은 아래 영상에서 다룬다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10252&quot;&gt;Make blazing fast lists and collection views&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Swift 5.5의 새로운 기능인 async, await에 대한 내용은 아래 영상에서 다룬다고 합니다. 이건 정말 유용하게 쓸 것 같네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://icksw.tistory.com/266&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Meet async / await in Swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10058/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Meet AsyncSequence&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Security and Privacy (보안, 개인 정보 보호)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 iOS 15의 UIKit에서 보안과 개인 정보 보호의 개선점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 시스템이 실제로 어떤 인터페이스와 상호작용하는지 확인할 수 있도록 새로운 기술을 추가했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상에서는 추가된 기술 중 앱에 영향을 줄 수 있는 세 가지에 대해서 알려준다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;688&quot; width=&quot;643&quot; height=&quot;410&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBPZI4/btrd9kRRpLb/veUZkst7TrMLE3NVgGpadK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBPZI4/btrd9kRRpLb/veUZkst7TrMLE3NVgGpadK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBPZI4/btrd9kRRpLb/veUZkst7TrMLE3NVgGpadK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBPZI4%2Fbtrd9kRRpLb%2FveUZkst7TrMLE3NVgGpadK%2Fimg.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;688&quot; width=&quot;643&quot; height=&quot;410&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 Location Button입니다. OS 15는 새로운 API를 도입해서 앱이 기기의 현재 위치에 대해 케이스별로 일회성 접근 권한을 부여하는 버튼을 포함할 수 있도록 한다고 합니다. 좀 더 자세한 내용은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10102&quot;&gt;Meet the Location Button&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit14.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6MSHS/btrd0y5pOxd/4M9AfJ4NqDWtsrstiks6K0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6MSHS/btrd0y5pOxd/4M9AfJ4NqDWtsrstiks6K0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6MSHS/btrd0y5pOxd/4M9AfJ4NqDWtsrstiks6K0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b6MSHS/btrd0y5pOxd/4M9AfJ4NqDWtsrstiks6K0/img.gif&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;344&quot; data-filename=&quot;UIKit14.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 내용으로 위와 같은 화면은 (붙여 넣기 하면 알람 배너가 나옴) 앱이 다른 앱에서 복사된 데이터에 접근할 때 나오는데요, iOS 15에서는 이러한 붙여넣기 인터페이스와 의도적으로 상호작용한 뒤 데이터에 접근한 것을 시스템에서 확인할 수 있을 때마다 배너를 제거한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;?? 음 사실 잘 이해가 가지는 않는 내용입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;In&amp;nbsp;iOS&amp;nbsp;15,&amp;nbsp;we're&amp;nbsp;eliminating&amp;nbsp;the&amp;nbsp;banner&amp;nbsp;any&amp;nbsp;time&amp;nbsp;the&amp;nbsp;system&amp;nbsp;can&amp;nbsp;confirm&amp;nbsp;that&amp;nbsp;the&amp;nbsp;data&amp;nbsp;was&amp;nbsp;accessed&amp;nbsp;after&amp;nbsp;deliberate&amp;nbsp;interaction&amp;nbsp;with&amp;nbsp;a&amp;nbsp;standard&amp;nbsp;system&amp;nbsp;paste&amp;nbsp;interface.&quot; 시스템이 확인할 때마다 배너를 제거한다니? 좀 더 읽어보겠습니다.&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-origin-width=&quot;742&quot; data-origin-height=&quot;514&quot; width=&quot;562&quot; height=&quot;389&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwTa7A/btrea1YQMee/BsYDP6GCjOIXVvHK6pkhN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwTa7A/btrea1YQMee/BsYDP6GCjOIXVvHK6pkhN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwTa7A/btrea1YQMee/BsYDP6GCjOIXVvHK6pkhN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwTa7A%2Fbtrea1YQMee%2FBsYDP6GCjOIXVvHK6pkhN1%2Fimg.png&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;514&quot; width=&quot;562&quot; height=&quot;389&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 붙여넣기 기능을 위해서 편집 메뉴에서 붙여넣기 버튼을 탭 하거나 하드웨어 키보드에서 Command-V를 입력하게 되는데, 이를 위한 몇 가지 API도 추가했다고 합니다. 새로운 API를 사용하면 알림 배너도 표시되지 않는다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Paste, Paste and Go, Paste and Search, Paste and Match Style가 있고 각각에 대해서 UIMenuController, UICommand를 사용하기 위한 UIResponder selector와 새로운 identifier를 위한 UIAction이 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음.. 즉 이 녀석들을 사용하면 아까 본 화면과 같이 붙여 넣기를 해도 배너가 나오지 않는다는 말이겠죠? 나중에 써보고 확인해보도록 해야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 14에서는 앱이 pasteboard에 숫자, 웹 URL, 웹 검색어가 있는지 확인할 수 있는 API를 도입했었는데, 이를 계산기와 사파리에서 사용하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15에서는 해당 API가 더 많은 타입을 감지하도록 개선되었다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;314&quot; width=&quot;648&quot; height=&quot;183&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6w5H9/btrd1zC4pb1/oAAAH6jYjNfJpBF9bEsCMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6w5H9/btrd1zC4pb1/oAAAH6jYjNfJpBF9bEsCMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6w5H9/btrd1zC4pb1/oAAAH6jYjNfJpBF9bEsCMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6w5H9%2Fbtrd1zC4pb1%2FoAAAH6jYjNfJpBF9bEsCMK%2Fimg.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;314&quot; width=&quot;648&quot; height=&quot;183&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 가능하던 것 외에도 다양한 타입을 감지할 수 있게 되었다고 하네요. 이 기능은&amp;nbsp;데이터 자체에 대한 접근 권한을 부여하지 않기 때문에 알림도 표시하지 않는다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트를 직접 분석하지 않고 데이터 값을 검색하는 API도 있는데요, 이러한 API는 standard paste interface를 사용한 이후가 아닌 다른 시간에 사용되는 경우 알림 배너가 나타난다고 합니다.&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-origin-width=&quot;1174&quot; data-origin-height=&quot;414&quot; width=&quot;783&quot; height=&quot;276&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIKUS6/btrec5mnpio/4mSSBQiSgN0guUSzlFavBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIKUS6/btrec5mnpio/4mSSBQiSgN0guUSzlFavBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIKUS6/btrec5mnpio/4mSSBQiSgN0guUSzlFavBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIKUS6%2Fbtrec5mnpio%2F4mSSBQiSgN0guUSzlFavBk%2Fimg.png&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;414&quot; width=&quot;783&quot; height=&quot;276&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 개인 정보 보호 기능은 iOS 14.5에서 추가된 위치와 붙여 넣기 인터페이스를 지원하는 기술을 기반으로 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIEventAttribution은 WebKit팀과 협업해서 만들었는데, WebKit의 Private Click Measurement 기능은 Web-to-Web 클릭 측정을 제공한다고 합니다. 그러다가 UIEventAttribution은 PCM을 UIKit으로 가지고 오고, App-to-Web 클릭 측정도 제공합니다. 이는 광고 클릭에 대한 개인 정보 보호 측정을 의미한다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 사용하기 위해서는 광고를 UIEventAttributionViews로 덮고 광고 탭에 대한 응답으로 열리는 URL과 함께 UIEventAttribution 객체를 전달하기만 하면 된다고 합니다. 이에 대한 자세한 내용은 아래 영상을 참고하라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10033&quot;&gt;Meet privacy-preserving ad attribution&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하고 영상은 끝나게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 15의 UIKit에는 정말 다양한 것들이 추가되었고, 어서 사용해보고 싶은 것들도 많이 추가된 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 iOS에서 drag and drop을 통해 앱 사이의 데이터 이동과 더 개선된 UIButton과 공유 영상과 같은 기능은 빨리 사용해보고 싶네요!&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;그럼 오늘 WWDC 영상인 &quot;What's new in UIKit&quot;은 여기까지 정리하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다. &lt;/p&gt;</description>
      <category>Apple/WWDC 2021</category>
      <category>2021</category>
      <category>Apple</category>
      <category>IOS</category>
      <category>Swift</category>
      <category>UIKit</category>
      <category>WWDC</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/264</guid>
      <comments>https://icksw.tistory.com/264#entry264comment</comments>
      <pubDate>Sun, 5 Sep 2021 21:33:30 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 문법] Swift 공식 문서 정리 - 28 - Concurrency</title>
      <link>https://icksw.tistory.com/262</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Swift 5.5에서 공식문서에 추가된 내용인 Concurrency를 읽고 정리한 글을 써보려고 합니다.&lt;/p&gt;
&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://docs.swift.org/swift-book/LanguageGuide/Concurrency.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift 공식 문서 - Concurrency&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Concurrency&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 5.5부터는 built in 방식으로 비동기 코드, 병렬 코드를 지원합니다. 비동기 코드는 일시 정지되었다가 나중에 다시 시작할 수 있지만 한 번에 프로그램의 한 부분만 실행하게 됩니다. 비동기로 코드를 작성하게 되면 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;병렬 코드의 경우에는 한 시점에 여러개의 코드들이 실행되는 것을 말합니다. 예를 들어 4 코어 프로세서가 있는 컴퓨터는 각 코어가 하나의 작업을 실행할 수 있으므로 4개의 코드를 동시에 실행할 수 있습니다. 병렬, 비동기 코드를 사용하는 프로그램은 한 번에 여러 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬, 비동기 코드로 인해 필요한 추가적인 스케줄링은 복잡성을 증가시킵니다. Swift를 사용하면 일부 컴파일 시간에 검사를 하는 방식으로 개발자의 의도를 표현 할 수 있는데요(이 부분이 Swift lets you express your intent in a way that enables soee compile-time checking&quot;인데.. 번역이 어렵네요), 예를 들어 actors를 사용하면 변경 가능한 상태에 안전하게 접근이 가능합니다. 하지만 문제가 있는 코드에 동시성을 추가한다고 해서 빠르게 실행되거나 정확성을 보장하지는 않습니다. 실제로 동시성을 추가하면 코드를 디버깅하기는 더 어려워질 때도 있습니다. 하지만 동시성을 사용해야 할 때 Swift 언어 자체에서 이를 지원하게 되면 Swift가 컴파일 시간에서 이를 파악할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글의 나머지 부분에서는 동시성이라는 용어를 비동기, 병렬 코드라는 의미로 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Note : Swift 5.5버전 이전에 동시성 코드를 작성한 경험이 있다면 스레드 작업에 익숙하실 텐데요, Swift의 동시성 모델은 스레드 위에 구축되지만 직접 상호 작용하지는 않습니다. Swift의 비동기 함수는 실행 중인 스레드를 포기할 수도 있습니다. 그러면 첫 번째 함수가 차단되는 동안 해당 스레드에서 다른 비동기 함수를 실행할 수 있게 됩니다.&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;Swift 언어 자체에서 제공하는 기능을 사용하지 않고도 동시성 코드를 작성하는 것은 가능하지만, 그렇게 하면 코드가 좀 더 복잡해집니다. 예를 들어 다음 코드는 사진 이름 목록을 다운로드하고 해당 목록의 첫 번째 사진을 다운로드하고, 첫 번째 사진을 사용자에게 보여주는 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626545285969&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;listPhotos(inGallery: &quot;Summer Vacation&quot;) { photoNames in
    let sortedNames = photoNames.sorted()
    let name = sortedNames[0]
    downloadPhoto(named: name) { photo in
        show(photo)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 간단한 코드에도 코드는 completion handler로 완료이후의 작업을 작성해야 하기 때문에 nested 클로저를 사용해야 합니다. 이러한 형태로 여러 개의 중첩이 있는 코드를 작성한다면 좀 더 복잡한 코드가 탄생하게 되겠죠?&lt;/p&gt;
&lt;h1&gt;Defining and Calling Asynchronous Functions&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 메서드는 실행 도중 일시정지 할 수 있는 특수한 종류의 메서드입니다. 이는 완료될 때까지 실행되거나, 오류가 발생하거나, 반환되지 않는 일반적인 메서드와는 다른 점이라고 할 수 있습니다. 비동기 메서드는 기존의 메서드와 동일하게 3가지 중 하나를 수행하지만 실행 도중 일시정지할 수 있다는 점이 다릅니다. 이를 위해 비동기 메서드의 내부에서 실행을 일시정지하는 위치를 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드가 비동기임을 나타내기 위해서는 throw를 사용하여 throw 메서드를 표시하는 방법과 유사하게 해당 선언에서 매개변수 뒤에 async 키워드를 작성하면 됩니다. 메서드가 값을 반환하는 경우, 반환 화살표 앞에 async를 작성합니다. 예를 들어 갤러리에 있는 사진의 이름을 가지고 오는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626545620725&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func listPhotos(inGallery name: String) async -&amp;gt; [String] {
    let result = // ... some asynchronous networking code ...
    return result
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 비동기 코드를 작성 할 땐 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;비동기 메서드를 호출하면 해당 메서드가 반환될 때까지 실행이 일지 정지됩니다. 호출이 중단될 가능성이 있는 지점을 표시하기 위해 호출하는 곳 앞에 wait라고 작성합니다. 이는 오류가 있을 수 있다고 표시하는 throw 함수를 호출할 때 try를 작성하는 것과 동일한 원리입니다. 비동기 메서드 내에서 실행 프름은 다른 비동기 메서드를 호출할 때에만 일시 정지됩니다. 이러한 일시정지는 절대 암시적이거나, 선점적이지 않습니다. 즉 가능한 모든 중단 지점이 await로 표시됩니다. 여기서 선점적이다는 말은 어떤 스레드가 실행하고 있다면 완료될 때까지 다른 스레드가 실행되지 않는 것을 말합니다.&lt;/p&gt;
&lt;p 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_1626545871709&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let photoNames = await listPhotos(inGallery: &quot;Summer Vacation&quot;)
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;listPhotos(inGallery:), downloadPhoto(named:) 메서드는 모두 네트워크 요청을 수행해야 하므로 완료되기에 시간이 걸릴 수 있습니다. 따라서 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;위 예제의 동시성을 이해하기 위해 가능한 실행순서는 아래와 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;위의 코드는 첫 번째 줄에서 실행을 시작하고 첫 번째 await까지 실행됩니다. 즉 listPhotos(inGallery:) 메서드를 호출하고 해당 메서드가 반환될 때까지 기다리는 동안 실행을 일시 정지합니다.&lt;/li&gt;
&lt;li&gt;위의 코드의 실행이 일시 정지되는 동안 동일한 프로그램의 다른 동시성 코드가 실행됩니다. 예를 들어 오랜 시간이 걸리는 백그라운드 작업인 새로운 사진 갤러리 목록을 계속해서 업데이트할 수도 있습니다. 이러한 코드는 await로 표시된 다음 일시정지 지점까지 혹은 실행 완료될 때까지 실행됩니다.&lt;/li&gt;
&lt;li&gt;listPhotos(inGallery:)가 반환되면 위 코드는 해당 지점에서 시작하여 계속해서 실행됩니다. 즉 반환된 값을 photoNames에 할당하게 됩니다.&lt;/li&gt;
&lt;li&gt;sortedNames 및 name을 정의하는 줄은 일반적인 동기 코드이므로 가능한 정지 지점은 없습니다.&lt;/li&gt;
&lt;li&gt;다음 await는 downloadPhtoo(named:) 메서드에 대한 호출을 표시합니다. 이 코드는 해당 메서드가 반환될 때까지 실행을 다시 일시 정지하여 다른 동시성 코드가 실행되도록 합니다.&lt;/li&gt;
&lt;li&gt;downloadPhoto(named:)가 반환된 값이 photos에 할당되고 slow(_:)를 호출할 때 인수로 전달됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await로 표시된 코드의 가능한 일시정지 지점은 비동기 메서드가 반환되기를 기다리는 동안 현재 코드 부분이 실행을 일시 중지할 수 있음을 나타냅니다. 이를 스레드 양보(Yielding)라고 부르기도 합니다. 그 이유는 뒤에서 Swift가 현재 스레드에서 코드 실행을 중단하고 다른 코드를 실행하기 때문입니다. await가 있는 코드는 실행을 일시정지할 수 있어야 하므로 프로그램의 특정 위치에서만 비동기 메서드를 호출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 메서드 또는 프로퍼티에 있는 코드&lt;/li&gt;
&lt;li&gt;@main으로 표시된 구조체, 클래스, 열거형의 static main() 메서드에 있는 코드&lt;/li&gt;
&lt;li&gt;이번 글의 이후에 나올 Unstructed Concurrency에서 보게 될 하위 작업의 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Note : Task.sleep(_:) 메서드는 동시성 작동방식을 배우기 위해 간단한 코드를 작성할 때 유용합니다. 이 메서드는 아무 작업도 수행하지 않고 그냥 시간을 기다리는 메서드입니다. 네트워크 작업을 시뮬레이션하는 등의 실험을 해보고 싶을 때 이 메서드를 사용하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;Asynchronous Sequences&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 섹션에서 본 listPhotos(inGallery:) 메서드는 배열의 모든 요소가 준비된 뒤 비동기적으로 전체 배열을 한 번에 반환합니다. 또 다른 접근 방식은 비동기 시퀀스를 사용하여 한 번에 컬렉션의 한 요소를 기다리는 것입니다. 비동기 시퀀스에 대한 반복은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626546948290&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 for-in 구문을 사용하는 대신에 위의 코드에서는 for와 await를 함께 씁니다. 비동기 메서드를 호출할 때와 마찬가지로 await를 작성하게 되면 일시정지 시점을 나타냅니다. for-await-in 구문은 각각의 반복에서 다음 요소를 사용할 수 있을 때까지 실행을 정지하고 기다리게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 만든 Sequence 프로토콜을 채택한 타입을 for-in 루프에서 사용 할 수 있는 것과 같은 방식으로 AsyncSequence 프로토콜을 채택한 타입을 만들어서 for-await-in 구문에서 사용하는 것도 가능합니다.&lt;/p&gt;
&lt;h1&gt;Calling Asynchronous Functions in Parallel&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await를 사용하여 비동기 메서드를 호출하면 한 번의 하나의 코드만 실행됩니다. 비동기 코드가 실행되는 동안 호출자는 다음 코드 줄을 실행하려고 이동하기 전에 해당 코드가 완료되는 것을 기다리게 됩니다. 예를 들어 갤러리에서 처음 세 장의 사진을 가지고 오려면 다음과 같이 downloadPhoto(named:) 메서드에 대한 세 번의 호출을 기다릴 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626547591513&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])

let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 접근 방식에는 단점이 있습니다. 다운로드가 비동기적이고 진행되는 동안 다른 작업을 수행할 수는 있지만 downloadPhoto(named:)는 한 시점에 하나만 실행된다는 것이죠. 하지만 개발자들은 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;비동기 메서드를 호출하고 주변의 코드와 병렬로 실행하기 위해서는 상수를 선언하기 전 let 앞에 async를 작성하고 상수를 사용할 때마다 await를 작성하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626547756815&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 downloadPhoto(named:)를 3번 호출하게 되는데, 모든 호출이 다른 호출의 완료를 기다리지 않고 실행되게 됩니다. 즉 동시에 3개의 메서드가 실행될 수 있습니다. 코드가 함수의 결과를 기다리고 일시정지할 필요가 없기 때문에 await를 표시하지 않아도 됩니다. 따라서 photos가 정의된 라인까지 실행이 되는데요, photos를 선언하는 시점에서는 사진들이 모두 다운이 되어 있어야 하므로 await를 작성하여 이를 기다려줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 이번 섹션에서 본 두 코드의 차이점에 대해 생각할 수 있는 방법입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다음 줄의 코드가 해당 함수의 결과에 따라 달라지면 await를 사용하여 비동기 함수를 호출합니다. 이는 순차적으로 수행되는 작업을 생성합니다.&lt;/li&gt;
&lt;li&gt;코드의 결과가 필요하지 않은 경우엔 async let을 사용하여 비동기 메서드를 호출합니다. 이렇게 하면 병렬로 수행할 수 있는 작업이 생성됩니다.&lt;/li&gt;
&lt;li&gt;await와 async-let은 모두 일시 중단된 동안 다른 코드를 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;두 경우 모두 비동기 메서드가 반환될 때까지 필요한 경우 일시정지를 해야 하므로 해당 지점을 await로 표시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 코드에서 두 가지 방법을 모두 사용할 수도 있습니다.&lt;/p&gt;
&lt;h1&gt;Task and Task Groups&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task(작업)는 프로그램의 일부로 비동기적으로 실행할 수 있는 작업 단위입니다. 모든 비동기 코드는 task의 일부로 실행되는데요, 이전 섹션에서 알아본 async-let 구문은 자식 작업을 생성합니다. 또한 작업 그룹을 만들고 해당 그룹에 하위 작업을 추가할 수 있습니다. 그러면 우선순위와 취소를 잘 제어할 수 있고 동적인 수의 작업을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업은 계층 구조로 정렬됩니다. 작업 그룹의 각 작업에는 동일한 상위 작업이 있으며, 각 작업에는 하위 작업이 있을 수도 있죠. 작업과 작업 그룹 간의 명시적 관계 때문에 이러한 접근 방식을 구조적 동시성이라고 합니다. 정확성에 대한 책임 중 일부는 사용자가 담당하지만, 작업 간의 명시적 부모, 자식 관계를 통해 Swift는 취소 전파와 같은 일부 동작을 처리할 수 있고 Swift는 컴파일 시간에 일부 오류를 감지할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626548303724&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await withTaskGroup(of: Data.self) { taskGroup in
    let photoNames = await listPhotos(inGallery: &quot;Summer Vacation&quot;)
    for name in photoNames {
        taskGroup.async { await downloadPhoto(named: name) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 그룸에 대한 더 많은 정보는 &lt;a href=&quot;https://developer.apple.com/documentation/swift/taskgroup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 알아보세요!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unstructured Concurrency&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 섹션에서 설명한 동시성에 대한 구조화된 접근 말고도 Swift는 구조화되지 않은 동시성도 지원합니다. 작업 그룹의 일부인 작업과 달리 구조화되지 않은 작업에는 상위 작업이 없습니다. 프로그램이 필요로 하는 방식으로 비정형 작업을 관리할 수 있는 완전한 유연함이 있지만, 정확성에 대한 책임은 개발자에게 있습니다. 현재 actor에서 실행되는 비정형 작업을 생성하려면, Task.init(priority:operation:) 생성자를 사용하면 됩니다. 현재 actor의 일부가 아닌 비정형 작업을 만들려면 Task.detached(priority:operation) 메서드를 호출합니다. 이러한 작업은 모두 작업과 상호 작용할 수 있는 task handle을 반환합니다. 예를 들어 이러한 작업의 결과를 기다리거나 취소할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626548569295&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let newPhoto = // ... some photo data ...
let handle = Task {
    return await add(newPhoto, toGalleryNamed: &quot;Spring Adventures&quot;)
}
let result = await handle.value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Detach Task를 관리하는 더 많은 정보는 &lt;a href=&quot;https://developer.apple.com/documentation/swift/task&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 알아보세요!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Task Cancellation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 동시성은 cooperative cancellation model(협력 취소 모델)을 사용합니다. 각 작업은 실행의 적절한 시점에서 취소되었는지에 대한 여부를 확인하고 적절한 방법으로 취소를 처리합니다. 수행 중인 작업에 따라 일반적으로 다음 중 하나를 의미합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CancellationError와 같은 오류 발생&lt;/li&gt;
&lt;li&gt;nil 또는 빈 컬렉션 반환&lt;/li&gt;
&lt;li&gt;부분적으로 완료된 작업을 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task.isCancelled의 값을 확인하고 자체 코드에서 취소를 처리합니다. 예를 들어 갤러리에서 사진을 다운로드하는 작업은 부분 다운로드를 삭제하고 네트워크 연결을 닫아야 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 취소를 수동으로 전파하려면 Task.cancel()을 호출하면 됩니다.&lt;/p&gt;
&lt;h1&gt;Actors&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이번엔 이번 글 여기저기서 나오던 Actor에 대해 알아보겠습니다. 클래스와 마찬가지로 Actor도 참조 타입입니다. 클래스와 달리 Actor는 한 번에 하나의 작업만 변경 가능한 상태에 접근할 수 있도록 허용하므로 여러 작업의 코드가 Actor의 동일한 인스턴스와 상호작용하는 것이 안전하게 됩니다. 온도를 기록하는 Actor코드를 한 번 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626548941334&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor 키워드를 사용하여 정의합니다. TemperatureLogger Actor에는 Actor 외부의 다른 코드가 접근할 수 있는 속성이 있고 Actor 내부의 코드만 최댓값을 업데이트할 수 있도록 max 속성을 정의했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체 및 클래스의 동일한 생성자 구문을 사용하여 Actor의 인스턴스를 생성할 수 있습니다. Actor의 프로퍼티나 메서드에 접근할 때 await를 사용하여 잠재적인 정지 지점을 표시합니다. 예제 코드를 한 번 볼까요?&lt;/p&gt;
&lt;pre id=&quot;code_1626549114403&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let logger = TemperatureLogger(label: &quot;Outdoors&quot;, measurement: 25)
print(await logger.max)
// Prints &quot;25&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예를 보면 logger.max에 접근하는 것은 일시정지가 가능한 지점입니다. Actor는 한 번에 하나의 작업만 변경 가능한 상태에 접근 할 수 있도록 허용하기 때문에 다른 작업 코드가 이미 logger에 접근 중인 경우에는 기다리게 됩니다. 마치 lock, semaphore를 사용하여 상호 배제를 적용해주는 것과 동일하게 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대조적으로 Actor의 코드에서는 Actor의 프로퍼티에 접근할 때 await를 작성하지 않습니다. 예를 들어 다음은 새로운 온도로 TemperatureLogger를 업데이트하는 방법입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626549329840&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension TemperatureLogger {
    func update(with measurement: Int) {
        measurements.append(measurement)
        if measurement &amp;gt; max {
            max = measurement
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update(with:) 메서드는 이미 Actor에서 실행 중 이므로 max와 같은 프로퍼티에 대한 접근을 await로 표시하지 않습니다. 이 방법은 Actor가 변경 가능한 상태와 상호작용하기 위해 한 번에 하나의 작업만 허용하는 이유 중 하나를 보여줍니다. Actor의 상태에 대한 일부 업데이트는 일시적으로 불변성을 깨트리는데요, TemperatureLogger Actor는 온도 목록과 최대 온도를 추적하고 새로운 측정값을 기록할 때 최대 온도를 업데이트합니다. 업데이트 도중에 새로운 측정이 추가되면 이는 logger의 &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;코드는 update(with:) 메서드를 호출합니다. 먼저 meaurements 배열을 업데이트 합니다.&lt;/li&gt;
&lt;li&gt;코드에서 최댓값을 업데이트 하기 전에 다른 코드에서 최대값과 온도 배열을 읽습니다.&lt;/li&gt;
&lt;li&gt;코드는 최대값을 변경하여 업데이트를 완료합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우 다른 곳에서 실행 중인 코드는 부정확한 정보에 접근하게 됩니다. Swift Actor를 사용하면 이러한 문제를 방지할 수 있습니다. Actor는 한 시점에 하나의 작업만 허용하고 해당 코드는 await로 정지 가능한 지점을 표시하기 때문입니다. update(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;클래스의 인스턴스에서와 같이 Actor 외부에서 이러한 프로퍼티에 접근하려고 하면 아래와 같이 컴파일 오류가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626549855077&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(logger.max)  // Error&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logger.max에 await 없이 접근하면 오류가 발생하는 이유는 actor의 프로퍼티는 actor의 독립 상태의 일부이기 때문입니다. Swift는 actor 내분의 코드만 actor의 로컬 상태에 접근 할 수 있도록 보장합니다. 이러한 보장을 actor isolation이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Swift/Swift_Documents</category>
      <category>Apple</category>
      <category>async</category>
      <category>Await</category>
      <category>concurrency</category>
      <category>document</category>
      <category>IOS</category>
      <category>Swift</category>
      <category>Thread</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/262</guid>
      <comments>https://icksw.tistory.com/262#entry262comment</comments>
      <pubDate>Sun, 18 Jul 2021 04:28:32 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Visitor Pattern (방문자) - 디자인 패턴 공부 24</title>
      <link>https://icksw.tistory.com/261</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다! &lt;/p&gt;
&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://icksw.tistory.com/260&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Template Method Pattern(템플릿 메서드)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Visitor Pattern에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;방문자 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Visitor Pattern(방문자)은 알고리즘을 작동하는 객체에서 분리할 수 있는 디자인 패턴입니다. Visotor를 사용하면 작업이 수행되는 객체의 클래스를 변경하지 않고도 새로운 알고리즘을 정의할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_7CB6A3A0D5ED-1.jpeg&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB42Sd/btq8GQi2pPS/SSBQjZPpDv8vVXdXo4R4pk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB42Sd/btq8GQi2pPS/SSBQjZPpDv8vVXdXo4R4pk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB42Sd/btq8GQi2pPS/SSBQjZPpDv8vVXdXo4R4pk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB42Sd%2Fbtq8GQi2pPS%2FSSBQjZPpDv8vVXdXo4R4pk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;386&quot; data-filename=&quot;IMG_7CB6A3A0D5ED-1.jpeg&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Visitor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Concrete Element를 매개변수로 사용할 수 있는 visit 메서드들을 정의합니다.&lt;/li&gt;
&lt;li&gt;언어가 오버 로딩을 지원하는 경우 메서드 이름이 같을 순 있지만 매개변수의 타입은 달라야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Visitor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 다른 Concrete Element에 맞게 메서드를 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Element
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Visitor를 Accept 하는 메서드를 정의합니다. Accept 메서드는 Visitor 객체를 매개변수로 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Element
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Accept 메서드를 구현합니다.&lt;/li&gt;
&lt;li&gt;Accept 메서드는 Element 객체에서 사용할 적절한 Visitor의 메서드를 호출하는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬렉션이나 트리와 같은 자료구조를 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;방문자 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체들이 복잡한 구조(트리 등)의 모든 Element에 대해 작업을 수행해야 하는 경우 Visitor Pattern을 사용하면 좋습니다. 또한 클래스 계층 구조에서 몇몇 단계에 있는 클래스에만 의미 있는 메서드가 존재할 때도 사용하면 좋습니다. 이를 위해 방문자 패턴은 새로운 메서드를 기존 클래스에 새로 만들기보다는 Visitor라는 별도의 클래스에 만들게 됩니다. 이러한 메서드를 사용하는 객체는 Visitor 객체의 메서드 중 하나를 선택해서 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 알람을 보내는 클라이언트가 있다고 가정해보겠습니다. 알람의 종류로는 email, sms, push가 있다고 가정해볼게요. 낮과 밤 모두 알람을 전송하다 보니 사용자들이 화를 내기 시작합니다. 그래서 앞으로는 밤에는 push 알람만 보내기로 결정하게 됩니다. 이러한 상황에서 클라이언트가 알람을 보낼 때 상황에 맞게 적절한 알람을 보내기 위해선 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Visitor Pattern을 사용하지 않는다면 시간에 따라 if문과 같은 조건문을 사용해서 알람을 보낼 방법을 찾고 해당 방법으로 알람을 전송해야 할 것 같습니다. 이렇게 만들게 되면 새로운 유형의 알람이 생기면 코드 전체를 수정해야 하는 불편함이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Visitor Pattern을 사용하여 클라이언트가 직접 알람을 보낼 객체를 찾는 대신 Visitor 객체가 알람을 보내기에 적절한지를 알려주면 어떨까요? 즉 Visitor 객체가 허용한 객체만 알람을 보내주도록 하는 것이죠. 이렇게 하면 새로운 타입의 알람이 만들어지더라도 Visitor부분만 수정해주면 되기 때문에 클라이언트는 그냥 계속 동일하게 사용할 수 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;방문자 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Open / Closed Principle(개방 / 폐쇄 원칙)을 준수합니다. 클래스를 변경하지 않고도 다른 클래스의 객체에 사용 할 수 있는 메서드를 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;Single Responsibility Principle(단일 책임 원칙)을 준수합니다. 동일한 동작을 하는 여러 개의 메서드를 동일한 클래스에 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;Visitor 객체는 다양한 객체로 작업하는 동안 다양한 정보를 모을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스가 Element 계층 구조에 추가, 제거될 때마다 모든 Visitor를 업데이트해줘야 합니다.&lt;/li&gt;
&lt;li&gt;Visitor 객체는 작업하는 Element의 private 프로퍼티, 메서드에 접근 권한이 없을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Visitor Pattern을 Swift로 구현해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 예로 들었던 메시지 알림 프로그램을 한 번 만들어볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Element를 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625393139865&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Element Interface
protocol Notification {
    var notificationType: String { get }
    func accept(visitor: NotificationPolicy) -&amp;gt; Bool
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음에는 Concrete Element들을 만들어줍니다. 지금 예에서는 Email, SMS, Push가 있겠죠?&lt;/p&gt;
&lt;pre id=&quot;code_1625393201942&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Element
class Email: Notification {

    let emailAddressOfSender: String
    var notificationType: String {
        return &quot;Email&quot;
    }
    init(emailAddressOfSender: String) {
        self.emailAddressOfSender = emailAddressOfSender
    }
    
    func accept(visitor: NotificationPolicy) -&amp;gt; Bool {
        visitor.isTurnedOn(for: self)
    }
}

// Concrete Element
class SMS: Notification {
    
    let phoneNumberOfSender: String
    var notificationType: String {
        return &quot;SMS&quot;
    }
    init(phoneNumberOfSender: String) {
        self.phoneNumberOfSender = phoneNumberOfSender
    }
    
    func accept(visitor: NotificationPolicy) -&amp;gt; Bool {
        visitor.isTurnedOn(for: self)
    }
}

// Concrete Element
class Push: Notification {
    
    let userIdOfSender: String
    var notificationType: String {
        return &quot;Push&quot;
    }
    init(userIdOfSender: String) {
        self.userIdOfSender = userIdOfSender
    }
    
    func accept(visitor: NotificationPolicy) -&amp;gt; Bool {
        visitor.isTurnedOn(for: self)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 이러한 Element들이 보내지기에 적합한 시간인지를 알려주기 위한 Visitor를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625393360479&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Visitor Interface
protocol NotificationPolicy {
    var policyType: String { get }
    func isTurnedOn(for email: Email) -&amp;gt; Bool
    func isTurnedOn(for sms: SMS) -&amp;gt; Bool
    func isTurnedOn(for push: Push) -&amp;gt; Bool
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 Concrete Visitor도 만들어 줍니다. 저는 밤, 낮 2개의 Visitor를 만들어줄게요.&lt;/p&gt;
&lt;pre id=&quot;code_1625393398750&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Visitor
class DayPolicyVisitor: NotificationPolicy {
    var policyType: String {
        return &quot;Day Policy&quot;
    }
    func isTurnedOn(for email: Email) -&amp;gt; Bool {
        return true
    }
    
    func isTurnedOn(for sms: SMS) -&amp;gt; Bool {
        return true
    }
    
    func isTurnedOn(for push: Push) -&amp;gt; Bool {
        return true
    }
}

// Concrete Visitor
class NightPolicyVisitor: NotificationPolicy {
    var policyType: String {
        return &quot;Night Policy&quot;
    }
    func isTurnedOn(for email: Email) -&amp;gt; Bool {
        return false
    }
    
    func isTurnedOn(for sms: SMS) -&amp;gt; Bool {
        return false
    }
    
    func isTurnedOn(for push: Push) -&amp;gt; Bool {
        return true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Concrete Element의 타입에 따라 Visitor객체가 현재 보낼 수 있는 알람인지 확인해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이를 사용하는 Client를 만들어주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625393485252&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Client {
    func alert(notifications: [Notification], policy: NotificationPolicy) {
        print(policy.policyType)
        notifications.forEach { notitication in
            if notitication.accept(visitor: policy) {
                print(&quot;\(notitication.notificationType) notification send complete&quot;)
            } else {
                print(&quot;\(notitication.notificationType) notification can't send now&quot;)
            }
        }
        print(&quot;\n&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 사용해보면 아래와 같아집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGS8KM/btq8Nvdtrpc/U4jqQgoa18OwiTQs7QMwv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGS8KM/btq8Nvdtrpc/U4jqQgoa18OwiTQs7QMwv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGS8KM/btq8Nvdtrpc/U4jqQgoa18OwiTQs7QMwv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGS8KM%2Fbtq8Nvdtrpc%2FU4jqQgoa18OwiTQs7QMwv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;122&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTLBx/btq8NdD7z76/cwnKKKXH83rshBpMWVo501/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTLBx/btq8NdD7z76/cwnKKKXH83rshBpMWVo501/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTLBx/btq8NdD7z76/cwnKKKXH83rshBpMWVo501/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTLBx%2Fbtq8NdD7z76%2FcwnKKKXH83rshBpMWVo501%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;155&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;155&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;즉 알람을 보낼 수 있는지에 대한 여부를 판별하는 알고리즘을 Visitor 객체에서 처리한 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Visitor Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Visitor Pattern이 행동 패턴 중 가장 어려운 패턴이었던 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 이해한 게 맞는지도 의문이네요 ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Visitor%20Pattern/Visitor%20Pattern&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;이번 Visitor Pattern을 끝으로 GoF의 모든 디자인 패턴을 공부했습니다.&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Swift</category>
      <category>Visitor</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/261</guid>
      <comments>https://icksw.tistory.com/261#entry261comment</comments>
      <pubDate>Sun, 4 Jul 2021 19:21:56 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Template Method Pattern (템플릿 메서드) - 디자인 패턴 공부 23</title>
      <link>https://icksw.tistory.com/260</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다! &lt;/p&gt;
&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://icksw.tistory.com/259&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Strategy Pattern(전략)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Template Method Pattern에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;템플릿 메서드 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Template Method(템플릿 메서드)는 부모 클래스에서 여러 메서드로 이루어진 알고리즘의 틀을 정의합니다. 이러한 알고리즘 틀을 Template Method라고 하며, 하위 클래스는 Template Method에서 단계별로 이루어진 메서드들을 override 할 수 있도록 만들어 구조를 변경하지 않고 알고리즘의 특정 단계를 재정의 할 수 있도록 하는 디자인 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_7C265F149D60-1.jpeg&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;761&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHqU1r/btq8IzUvogd/EhulmBWrmwgTRq1sQoaM80/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHqU1r/btq8IzUvogd/EhulmBWrmwgTRq1sQoaM80/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHqU1r/btq8IzUvogd/EhulmBWrmwgTRq1sQoaM80/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHqU1r%2Fbtq8IzUvogd%2FEhulmBWrmwgTRq1sQoaM80%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;480&quot; data-filename=&quot;IMG_7C265F149D60-1.jpeg&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;761&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Abstract Class (Application)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Abstract Class는 알고리즘을 단계적으로 작동하는 메서드들과 이들을 실제로 호출하는 Template Method를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Class (My Application)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Abstract Class에서 정의한 단계적으로 작동하는 메서드들은 override 할 수 있지만 Template Method는 override 할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;템플릿 메서드는 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;예를 들어 여러 확장자의 파일들에서 원하는 데이터를 추출하는 작업을 한다고 가정해볼게요. PDF, Word, Excel 파일에서 원하는 데이터를 얻고 싶은 경우 어떤 과정으로 데이터를 얻을 수 있을까요? 일단 간단하게 생각해보면 아래와 같은 단계가 필요할 거 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 파일에서 데이터를 가지고 옵니다.&lt;/li&gt;
&lt;li&gt;가지고 온 데이터를 원하는 데이터로 처리합니다.&lt;/li&gt;
&lt;li&gt;처리된 데이터를 분석합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 1번 과정만 다르고 2,3번 과정은 모두 동일하게 작동되어도 문제가 없어보입니다. 하지만 만약 3개의 파일을 처리하기 위한 코드를 모두 따로 구현하면 비효율적이겠죠? 또한 다른 확장자를 분석하는 코드도 추가하려면 더 많은 작업이 필요하게 됩니다. 따라서 동일한 코드를 3번 쓰기보다는 템플릿 메서드 패턴을 사용해서 1번 과정만 3개 만들고 나머지 2,3번 과정은 동일한 코드로 구현하면 이와 같은 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;템플릿 메서드의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 알고리즘의 특정 부분을 구현해도 알고리즘의 다른 부분은 영향을 덜 받도록 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;중복된 코드를 슈퍼 클래스에서 한 번만 정의해도 되기 때문에 효율적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 클라이언트는 이미 정의된 알고리즘만 사용할 수 있기 때문에 제한받는 상황이 올 수 있습니다.&lt;/li&gt;
&lt;li&gt;Liskov Subsititution Priciple을 위반 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;템플릿 메서드에 필요한 단계가 많다면 유지하기 어려울 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Template Method Pattern을 Swift로 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 본 다양한 확장자 파일에서 데이터를 가져와서 분석하는 상황을 만들어보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아까 정의해본 과정을 한 번 다시 보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 파일에서 데이터를 가지고 옵니다.&lt;/li&gt;
&lt;li&gt;가지고 온 데이터를 원하는 데이터로 처리합니다.&lt;/li&gt;
&lt;li&gt;처리된 데이터를 분석합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 각각의 Concrete Class에서 override 할 메서드는 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;그럼 먼저 Template Method 및 다른 메서드들을 구현한 Abstract Class를 먼저 구현해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1625313217565&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Abstract Class
class DataMining {
    // Template Method
    final func dataMining() {
        getData()
        dataProcess()
        dataAnalysis()
    }
    
    func getData() {
        print(&quot;데이터를 불러옵니다.&quot;)
    }
    func dataProcess() {
        print(&quot;데이터 처리완료&quot;)
    }
    func dataAnalysis() {
        print(&quot;데이터 분석완료\n&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Template Method 역할을 하는 dataMining()메서드는 더 이상 override 할 수 없도록 만들어 줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Concrete Class를 구현해야하는데요, 각각의 Concrete Class에서 override 할 메서드는 getData()뿐입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625313369924&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Class
class PDFFileDataMining: DataMining {
    override func getData() {
        print(&quot;PDF File 데이터를 불러옵니다.&quot;)
    }
}
// Concrete Class
class WordFileDataMining: DataMining {
    override func getData() {
        print(&quot;Word File 데이터를 불러옵니다.&quot;)
    }
}
// Concrete Class
class ExcelFileDataMining: DataMining {
    override func getData() {
        print(&quot;Excel File 데이터를 불러옵니다.&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 3개의 Concrete Class를 구현했지만 실제로 각각의 class에서 구현한 메서드는 getData() 하나뿐인 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 실제로 이를 사용할 Client를 구현해볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1625313437914&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum FileType {
    case pdf
    case word
    case excel
}
class Client {
    static func dataMining(fileType: FileType) {
        switch fileType {
        case .pdf:
            PDFFileDataMining().dataMining()
        case .word:
            WordFileDataMining().dataMining()
        case .excel:
            ExcelFileDataMining().dataMining()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 파일 타입이 있기 때문에 열거형도 하나 만들어줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 한 번 사용해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9yyYB/btq8IeXKDdL/wcsZxdH6tyf1gmYu5rIes1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9yyYB/btq8IeXKDdL/wcsZxdH6tyf1gmYu5rIes1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9yyYB/btq8IeXKDdL/wcsZxdH6tyf1gmYu5rIes1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9yyYB%2Fbtq8IeXKDdL%2FwcsZxdH6tyf1gmYu5rIes1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;58&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pyty6/btq8HT0AMjE/t0k6acMQHEyT3tBZLvxWW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pyty6/btq8HT0AMjE/t0k6acMQHEyT3tBZLvxWW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pyty6/btq8HT0AMjE/t0k6acMQHEyT3tBZLvxWW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpyty6%2Fbtq8HT0AMjE%2Ft0k6acMQHEyT3tBZLvxWW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;194&quot; height=&quot;179&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 3개의 파일 타입을 처리 할 수 있는 코드를 만들었지만 데이터 처리와 분석 코드는 모두 Abstract Class에서 구현한 메서드를 그대로 사용하고 getData()만 각각의 서브 클래스에서 구현해준 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Template Method Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Template%20Method%20Pattern/Template%20Method%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>Method</category>
      <category>pattern</category>
      <category>Swift</category>
      <category>template</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/260</guid>
      <comments>https://icksw.tistory.com/260#entry260comment</comments>
      <pubDate>Sat, 3 Jul 2021 21:07:30 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Strategy Pattern (전략) - 디자인 패턴 공부 22</title>
      <link>https://icksw.tistory.com/259</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다! &lt;/p&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 Pattern(상태)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Strategy Pattern에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;전략 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Strategy Pattern(전략)은 Algorithm Family를 정의하고 각 알고리즘을 캡슐화 한 뒤 런타임에서 알고리즘을 서로 바꿔 사용할 수 있는 디자인 패턴입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_273130150502-1.jpeg&quot; data-origin-width=&quot;1343&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzHKtu/btq8yr2Nn9S/tVxrkgMimVVzWrJJngjKk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzHKtu/btq8yr2Nn9S/tVxrkgMimVVzWrJJngjKk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzHKtu/btq8yr2Nn9S/tVxrkgMimVVzWrJJngjKk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzHKtu%2Fbtq8yr2Nn9S%2FtVxrkgMimVVzWrJJngjKk0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;366&quot; data-filename=&quot;IMG_273130150502-1.jpeg&quot; data-origin-width=&quot;1343&quot; data-origin-height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Strategy (Compositor)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지원되는 모든 알고리즘에 사용되는 공통적인 인터페이스를 정의합니다.&lt;/li&gt;
&lt;li&gt;Context는 Strategy 인터페이스를 사용하여 Concrete Strategy에 정의된 알고리즘을 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Strategy
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Strategy 인터페이스를 사용하여 알고리즘을 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Context (Composition)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Concrete Strategy 객체로 구성됩니다.&lt;/li&gt;
&lt;li&gt;Strategy 객체에 대한 참조를 유지합니다.&lt;/li&gt;
&lt;li&gt;Strategy가 데이터에 접근 할 수 있는 인터페이스를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;전략 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;h1&gt;전략 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;런타임에서 객체 내부에서 사용되는 알고리즘을 변경 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;알고리즘을 사용하는 코드와 알고리즘을 구현하는 코드를 분리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Open / Closed Principle(개방 폐쇄 원칙)을 준수합니다. Context를 변경하지 않고도 새로운 Strategy를 도입할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알고리즘이 몇 개 없고 변경되는 일도 거의 없는 경우 전략 패턴의 도입이 오히려 복잡성을 증가시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 적절한 Strategy를 선택하기 위해서는 각각의 차이점을 알고 있어야 합니다.&lt;/li&gt;
&lt;li&gt;Strategy, Context간 통신 오버헤드가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Strategy Pattern을 Swift로 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 본 네비게이션 문제를 구현해보면 재밌을 거 같으니 한 번 해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Strategy 인터페이스 역할을 할 프로토콜을 하나 정의해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625057514056&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Strategy
protocol Strategy {
    func algorithmExecute()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Strategy 프로토콜을 채택하는 알고리즘들을 만들어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 네비게이션으로 자동차, 도보, 자전거 경로를 알고 싶으니 Strategy 프로토콜을 채택한 3개의 클래스를 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625057586526&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Strategy
class CarRoute: Strategy {
    func algorithmExecute() {
        print(&quot;자동차 경로 찾기 완료!\n&quot;)
    }
}

// Concrete Strategy
class WalkRoute: Strategy {
    func algorithmExecute() {
        print(&quot;도보 경로 찾기 완료!\n&quot;)
    }
}

// Concrete Strategy
class BikeRoute: Strategy {
    func algorithmExecute() {
        print(&quot;자전거 경로 찾기 완료!\n&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;그런 뒤 만든 알고리즘들을 교체해가며 사용할 Context를 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625057635404&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Context
class Navigation {
    private var routeAlgorithm: Strategy?
    
    func execute() {
        self.routeAlgorithm?.algorithmExecute()
    }
    
    func setStrategy(strategy: Strategy) {
        self.routeAlgorithm = strategy
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Strategy 객체를 참조하고 있는 Context 클래스를 만들었다면 이제 모두 구현했습니다.&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;762&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ClOpR/btq8wmVKNlT/ZwpKZHayXtFqKOzUgAmuIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ClOpR/btq8wmVKNlT/ZwpKZHayXtFqKOzUgAmuIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ClOpR/btq8wmVKNlT/ZwpKZHayXtFqKOzUgAmuIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FClOpR%2Fbtq8wmVKNlT%2FZwpKZHayXtFqKOzUgAmuIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;762&quot; height=&quot;314&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bajEwH/btq8ujlFIEt/eICBC5WiORWHAnJtyOaQZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bajEwH/btq8ujlFIEt/eICBC5WiORWHAnJtyOaQZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bajEwH/btq8ujlFIEt/eICBC5WiORWHAnJtyOaQZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbajEwH%2Fbtq8ujlFIEt%2FeICBC5WiORWHAnJtyOaQZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;270&quot; height=&quot;154&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결과를 보면 런타임에서 계속해서 Strategy 객체를 변경하며 다른 알고리즘을 사용하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Strategy Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Strategy%20Pattern/Strategy%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Stategy</category>
      <category>Swift</category>
      <category>전략</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/259</guid>
      <comments>https://icksw.tistory.com/259#entry259comment</comments>
      <pubDate>Wed, 30 Jun 2021 22:00:03 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인패턴] State Pattern (상태) - 디자인 패턴 공부 21</title>
      <link>https://icksw.tistory.com/258</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다! &lt;/p&gt;
&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://icksw.tistory.com/257&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Observer Pattern(옵저버)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 State Pattern에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;상태 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State Pattern은 런타임에서 내부의 상태가 변경 될 때 객체가 동작을 변경할 수 있도록 하여 마치 객체가 클래스를 변경하는 것처럼 보이게 하는 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_C33E71E0A935-1.jpeg&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJ0LT/btq8hmCNRyD/KRadlUgABBhWHlFlo4YBW1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJ0LT/btq8hmCNRyD/KRadlUgABBhWHlFlo4YBW1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJ0LT/btq8hmCNRyD/KRadlUgABBhWHlFlo4YBW1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJ0LT%2Fbtq8hmCNRyD%2FKRadlUgABBhWHlFlo4YBW1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;395&quot; data-filename=&quot;IMG_C33E71E0A935-1.jpeg&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context는 Concrete State 객체 중 하나에 대한 참조를 저장하고 모든 State의 작업을 위임합니다.&lt;/li&gt;
&lt;li&gt;Context는 State Interface를 통해 State 객체와 통신합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;State
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State의 메서드를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete State
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State Interface에서 정의된 메서드들을 구체화합니다.&lt;/li&gt;
&lt;li&gt;Concrete State는 Context 객체를 역으로 참조 할 수도 있습니다. 이러한 참조를 통해 State가 Context에서 필요한 정보를 가지고 오고 State를 변화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;상태 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP/IP 통신을 아시나요? TCP 통신은 커넥션이 만들어져야 할 수 있는데요, 즉 커넥션이 만들어진 상태, 그렇지 않은 상태로 나뉘게 됩니다. 두 상태에 따라 네트워크가 연결 될 수도, 연결 실패할 수도 있는 거죠. 즉 네트워크 연결 상태에 따라 사용할 수 있는 기능들이 다릅니다. 이렇게 상태에 따라 객체의 기능이 달라져야 할 때 State Pattern을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실생활에서 예를 찾아보면, 예전 iPhone에는 Home 버튼이 있죠? 이 버튼을 잠금 상태에서 누를 때, 앱을 사용중에 누를 때와 같이 스마트폰의 상태에 따라 동일한 버튼의 기능이 달라지는 경우가 있습니다. 이런 경우도 State Pattern이라고 할 수 있겠죠?&lt;/p&gt;
&lt;h1&gt;상태 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Single Responsibility Principle(단일 책임 원칙)을 준수합니다.&lt;/li&gt;
&lt;li&gt;기존의 State, Context 클래스를 변경하지 않고 새로운 State를 도입할 수 있기 때문에 Open / Closed Principle(개방 / 폐쇄 원칙)을 준수합니다.&lt;/li&gt;
&lt;li&gt;State 객체에 인스턴스 변수가 없는 경우 Context는 State 객체를 공유할 수 있기 때문에 &lt;a href=&quot;https://icksw.tistory.com/247&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flyweight 패턴&lt;/a&gt;처럼 Context의 코드를 단순화 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State가 몇 개 없거나 변경될 이유가 거의 없을 땐 패턴을 도입하는 것이 비효율적일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 State Pattern을 Swift로 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State Pattern으로 만들어 볼 상황은 유튜브 프리미엄을 결제한 상태와 그렇지 않은 상태를 만들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브 프리미엄을 결제하면, 백그라운드에서도 유튜브 영상을 재생 할 수 있는데요, 이를 간단하게 한 번 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 먼저 Context 역할을 할 YoutubeApp 클래스를 하나 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624895583301&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Context
class YoutubeApp {
    var youtubePremiumState: State
    
    init(subscribeState: State) {
        self.youtubePremiumState = subscribeState
    }
    
    func subscribe() {
        print(&quot;\n유튜브 프리미엄 구독 시작\n&quot;)
        self.youtubePremiumState = SubscribeState()
    }
    
    func unSubscribe() {
        print(&quot;유튜브 프리미엄 구독 해지\n&quot;)
        self.youtubePremiumState = UnSubscribeState()
    }
    
    func clickHomeButton() {
        self.youtubePremiumState.playBackground()
    }
    
    func clickAppIcon() {
        self.youtubePremiumState.playForeground()
    }
    
    func clickDownload() {
        self.youtubePremiumState.videoDownload()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브 앱에서는 다운로드, 앱 아이콘 클릭, 홈버튼 클릭과 같은 메서드가 존재하고 구독, 구독해지 메서드도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤엔 State 프로토콜을 하나 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624895640406&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// State Interface
protocol State {
    func playBackground()
    func playForeground()
    func videoDownload()
}&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;이번엔 State 프로토콜을 채택한 Concrete State인 유튜브 프리미엄 구독 상태, 구독 해지 상태를 만들면 될 것 같아요&lt;/p&gt;
&lt;pre id=&quot;code_1624895683945&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete State
class SubscribeState : State {
    func playBackground() {
        print(&quot;결제를 해서 백그라운드에서도 재생중&quot;)
    }
    func playForeground() {
        print(&quot;영상 재생 중&quot;)
    }
    func videoDownload() {
        print(&quot;결제를 해서 비디오 다운로드 가능&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1624895722218&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete State
class UnSubscribeState: State {
    func playBackground() {
        print(&quot;결제를 안하면 백그라운드에서는 재생 할 수 없어요.&quot;)
    }
    func playForeground() {
        print(&quot;영상 재생 중&quot;)
    }
    func videoDownload() {
        print(&quot;결제를 안하면 비디오 다운로드가 불가능해요.&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든 뒤 실행을 해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJFNO3/btq8kJRFiqJ/2ddP8O3vrkjR9MHOEesKs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJFNO3/btq8kJRFiqJ/2ddP8O3vrkjR9MHOEesKs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJFNO3/btq8kJRFiqJ/2ddP8O3vrkjR9MHOEesKs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJFNO3%2Fbtq8kJRFiqJ%2F2ddP8O3vrkjR9MHOEesKs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;308&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC155i/btq8jK4ncNw/jdwKfm4OMEsuJKhNBl6GJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC155i/btq8jK4ncNw/jdwKfm4OMEsuJKhNBl6GJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC155i/btq8jK4ncNw/jdwKfm4OMEsuJKhNBl6GJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC155i%2Fbtq8jK4ncNw%2FjdwKfm4OMEsuJKhNBl6GJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;399&quot; height=&quot;211&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결과를 보면 처음에는 구독 해제 상태로 앱이 시작됩니다. 그러다 subscribe() 메서드를 호출해서 유튜브 프리미엄 구독을 시작하게 되는 순간부터 백그라운드 재생, 비디오 다운로드가 가능해지게 되는 것을 볼 수 있습니다.&lt;/p&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 Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 구현 할 때 꽤나 재밌게 구현했던 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/State%20Pattern/State%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>STATE</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/258</guid>
      <comments>https://icksw.tistory.com/258#entry258comment</comments>
      <pubDate>Tue, 29 Jun 2021 00:59:20 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Observer Pattern (옵저버) - 디자인 패턴 공부 20</title>
      <link>https://icksw.tistory.com/257</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다! &lt;/p&gt;
&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://icksw.tistory.com/255&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Memento Pattern(메멘토)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Observer Pattern(옵저버)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;옵저버 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observer Pattern(옵저버)이란 관찰 중인 객체에서 발생하는 이벤트를 여러 다른 객체에 알리는 메커니즘을 정의할 수 있는 디자인 패턴입니다. iOS에서는 Swift 5.1 버전부터 Combine 프레임워크에 Publisher가 추가되어 이를 사용할 수 있고 NotificationCenter도 비슷하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_DE091FBE3C71-1.jpeg&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;755&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/znH5L/btq8gxJDVj9/0RSqdbd7eYNNZwi0BAlTG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/znH5L/btq8gxJDVj9/0RSqdbd7eYNNZwi0BAlTG0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/znH5L/btq8gxJDVj9/0RSqdbd7eYNNZwi0BAlTG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FznH5L%2Fbtq8gxJDVj9%2F0RSqdbd7eYNNZwi0BAlTG0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;445&quot; data-filename=&quot;IMG_DE091FBE3C71-1.jpeg&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;755&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subject (Publisher)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observer들을 가지고 있으며 개수는 제한이 없습니다.&lt;/li&gt;
&lt;li&gt;Observer들을 추가, 제거하는 인터페이스를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Subject (Publisher)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Concrete Observer 객체의 상태를 저장합니다.&lt;/li&gt;
&lt;li&gt;상태가 변경되면 Observer(Subscriber)에게 알립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Observer (Subscriber)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 변경 사항을 알려야하는 객체에 대한 Update 인터페이스를 제공합니다.&lt;/li&gt;
&lt;li&gt;상태가 변경되면&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Observer (Subscriber)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Concrete Subject (Publisher) 객체에 대한 참조를 유지합니다.&lt;/li&gt;
&lt;li&gt;Subject(Publisher)의 상태와 일관성을 유지합니다.&lt;/li&gt;
&lt;li&gt;객체의 상태와 일관성을 유지하기 위해 update 인터페이스를 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;옵저버 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 객체의 상태가 변경될 때마다 어떤 행동을 하고 싶다면 옵저버 패턴을 사용하면 됩니다. 이러한 패턴은 iOS에서는 ViewController에 Observer(Subscriber)가 있고, Model에 Subject(Publisher)가 있는 MVC 패턴에서 사용할 수 있습니다. 이를 통해 Model은 ViewController의 타입에 대해 알 필요 없이 상태가 변경될 때마다 이를 ViewController에 전달할 수 있습니다. 따라서 여러 개의 ViewController가 하나의 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;현실에서 옵저버 패턴을 사용하는 상황을 이해해보자면, 애플 매장에 아이폰이 매진 상태라고 해보겠습니다. 그래서 아이폰이 입고될 때 DB의 모든 고객에게 아이폰 입고 알림을 주게 되면 누군가에게는 좋은 알림이지만 누군가에게는 스팸이 될 수 있겠죠? 따라서 아이폰이 입고될 때 알림을 받고 싶은 고객에 대해서만 해당 알림을 주는 등의 방법을 사용하면 좋을 것 같아요. 이럴 때 사용할 수 있는 방법이 옵저버 패턴입니다.&lt;/p&gt;
&lt;h1&gt;옵저버 패턴의 결과?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Open / Close 원칙을 지킬 수 있습니다. Subject(Publisher)의 코드를 수정하지 않고 새로운 Observer(Subscriber) 클래스를 추가할 수 있습니다. 물론 그 반대도 가능합니다.&lt;/li&gt;
&lt;li&gt;런타임에서 객체간 관계를 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observer(Subscriber)에게 알림이 가는 순서는 보장하지 않습니다.&lt;/li&gt;
&lt;li&gt;Observer, Subject의 관계가 잘 정의되지 않으면 원하지 않는 동작이 발생할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Observer Pattern을 Swift 언어로 간단하게 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine 프레임워크나 NotificationCenter로 구현 할 수도 있지만, 그냥 직접 간단하게 한 번 구현해볼게요.&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;먼저 Subject(Publisher) 인터페이스를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624807057332&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Subject(Publisher) Interface
protocol Publisher {
    var observers: [Observer] { get set }
    func subscribe(observer: Observer)
    func unSubscribe(observer: Observer)
    func notify(message: String)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵저버를 추가하는 subscribe, 옵저버를 제거하는 unSubscribe, 추가된 옵저버들에게 알림을 보내는 notify 메서드를 가져야 하는 프로토콜을 정의합니다. 그런 뒤 여기서 사용하는 Observer 프로토콜을 정의해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624807137927&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Observer(Subscriber) Interface
protocol Observer {
    var id: String { get set }
    func update(message: String)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Publisher 역할을 하는 Apple Store 클래스를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624807163340&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Subject(Publisher)
class AppleStore: Publisher {
    var observers: [Observer]
    
    init(observers: [Observer]) {
        self.observers = observers
    }
    
    func subscribe(observer: Observer) {
        self.observers.append(observer)
    }
    
    func unSubscribe(observer: Observer) {
        if let index = self.observers.firstIndex(where: { $0.id == observer.id }) {
            self.observers.remove(at: index)
        }
    }
    
    func notify(message: String) {
        for observer in observers {
            observer.update(message: message)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 AppleStore의 알람을 수신할 Customer 클래스도 정의해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624807193907&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Observer(Subscriber)
class Customer: Observer {
    var id: String
    init(id: String) {
        self.id = id
    }
    func update(message: String) {
        print(&quot;\(id)님 \(message)수신\n&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;이렇게 하면 간단하게 Observer Pattern을 모두 구현 하게 됩니다.&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;742&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xQtxL/btq8ewEW8Gb/AAn0tNcujAkEcJms2UW9C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xQtxL/btq8ewEW8Gb/AAn0tNcujAkEcJms2UW9C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xQtxL/btq8ewEW8Gb/AAn0tNcujAkEcJms2UW9C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxQtxL%2Fbtq8ewEW8Gb%2FAAn0tNcujAkEcJms2UW9C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;364&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0UUrk/btq8jK2A2fc/E0HVKKoNXb4KDmumaPbu90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0UUrk/btq8jK2A2fc/E0HVKKoNXb4KDmumaPbu90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0UUrk/btq8jK2A2fc/E0HVKKoNXb4KDmumaPbu90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0UUrk%2Fbtq8jK2A2fc%2FE0HVKKoNXb4KDmumaPbu90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;404&quot; height=&quot;170&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;170&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;appleStore의 알림을 받는 Customer 객체인 pingu, roby를 추가해주고 notify 메서드를 호출하면 2명의 Customer 객체에 잘 전달되는 것을 볼 수 있습니다. 그런 뒤 roby를 unSubscribe로 제거해준 뒤 notify 메서드를 호출하면 Pingu 객체에만 전달되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Observer Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 사용하던 패턴인데 실제로 구현해보니 재밌었던 것 같아요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Observer%20Pattern/Observer%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>Observer</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/257</guid>
      <comments>https://icksw.tistory.com/257#entry257comment</comments>
      <pubDate>Mon, 28 Jun 2021 00:24:23 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] Class와 Struct의 차이점?</title>
      <link>https://icksw.tistory.com/256</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 iOS 개발에 쓰이는 Swift 언어에서 Class, Struct의 차이점이라는 주제를 가지고 글을 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 개발자로 면접을 준비하다 보면 Class, Struct의 차이점이라는 질문을 자주 접하게 됩니다. 아주 간단하게 차이점을 보자면 &lt;b&gt;&quot;Class는 참조타입이고 ARC로 메모리 관리를 한다. Struct는 값 타입이다.&quot;&lt;/b&gt; 정도로 표현 할 수 있을 거 같습니다. 이 질문을 통해 ARC를 통한 메모리 관리, 참조 타입과 값 타입의 차이점 등을 함께 답변할 수 있을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이 iOS 개발자 면접에서 자주 등장하는 해당 질문이 iOS 개발자에게 어떤 의미를 갖는지 좀 더 생각하게 되었고 좀 더 깊이 공부하게 되었습니다. 공부를 하다 보니 깨달은 것은 &lt;b&gt;&quot;개발자가 가장 중요하게 고려해야 할 것 중 하나는 성능이며 Class, Struct의 차이점을 확실히 알고 있다면 성능개선을 할 수 있다&quot;&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;즉 면접 질문에서 &quot;Class, Struct의 차이점은 무엇인가요?&quot;를 묻는 것은 단순히 &lt;b&gt;Swift 공식문서에 나와있는 기본적인 내용뿐만 아니라 좀 더 깊은 내용에도 관심이 있고 공부를 했는지, 그리고 결국 그렇게 깊이 공부를 했다면 성능 개선을 할 수 있는 개발자가 될 수 있다고 생각할 수 있기에 묻는다고 개인적으로 생각&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;그럼 일단 공식문서에서 제공하는 Swift의 Class, Struct의 &lt;b&gt;공통점&lt;/b&gt;부터 살펴보겠습니다.&lt;/p&gt;
&lt;h1&gt;Class, Struct의 공통점&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 저장할 프로퍼티를 선언할 수 있습니다.&lt;/li&gt;
&lt;li&gt;함수적 기능을 하는 메서드를 선언 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;내부 값에. 을 사용하여 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;생성자를 사용해 초기 상태를 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;extension을 사용하여 기능을 확장할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Protocol을 채택하여 기능을 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 공식문서에서 제공하는 Class, Struct의 &lt;b&gt;차이점&lt;/b&gt;도 살펴보겠습니다.&lt;/p&gt;
&lt;h1&gt;Class (클래스)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조 타입입니다.&lt;/li&gt;
&lt;li&gt;ARC로 메모리를 관리합니다.&lt;/li&gt;
&lt;li&gt;같은 클래스 인스턴스를 여러 개의 변수에 할당한 뒤 값을 변경시키면 할당한 모든 변수에 영향을 줍니다. (메모리만 복사)&lt;/li&gt;
&lt;li&gt;상속이 가능합니다.&lt;/li&gt;
&lt;li&gt;타입 캐스팅을 통해 런타임에서 클래스 인스턴스의 타입을 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;deinit을 사용하여 클래스 인스턴스의 메모리 할당을 해제할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Struct (구조체)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 타입입니다.&lt;/li&gt;
&lt;li&gt;구조체 변수를 새로운 변수에 할당할 때마다 새로운 구조체가 할당됩니다.&lt;/li&gt;
&lt;li&gt;즉 같은 구조체를 여러 개의 변수에 할당한 뒤 값을 변경시키더라도 다른 변수에 영향을 주지 않습니다. (값 자체를 복사)&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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_1623166843423&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SimpleClass {
    var count: Int = 0
    
    deinit {
        print(&quot;할당 해제&quot;)
    }
}
struct SimpleStruct {
    var count: Int = 0
}

var class1 = SimpleClass()
var class2 = class1
var class3 = class1

class3.count = 3

print(class1.count) // class3의 값을 변경했지만 참조타입이므로 class1도 변경 되는 것을 볼 수 있습니다.

var struct1 = SimpleStruct()
var struct2 = struct1
var struct3 = struct1

struct2.count = 2
struct3.count = 3

print(struct1.count) // 0
print(struct2.count) // 2 &amp;lt;- 구조체는 값 타입이므로 항상 새로운 메모리가 할당됩니다.
print(struct3.count) // 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 클래스는 참조 타입이라 같은 클래스 객체를 할당한 변수의 값을 변경시키면 참조된 객체의 값이 변경되고, 구조체는 값 타입이기 때문에 같은 구조체 객체를 할당하더라도 매번 새로운 메모리가 할당되어 값을 변경하더라도 다른 구조체 변수에 영향을 주지 않는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Class와 ARC, Retain Cycle&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 간단하게 Swift에서 참조 타입의 메모리 관리를 위한 ARC도 간단하게 코드로 보고 가겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623167295813&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ARC
print(&quot;ARC\n&quot;)
var classARC1: SimpleClass? = SimpleClass()
print(CFGetRetainCount(classARC1)) // 변수에 할당한 객체의 경우 2가 기본 값

var classARC2 = classARC1
print(CFGetRetainCount(classARC1)) // 참조 카운트가 1 증가한 3이 됩니다.

classARC1 = nil
print(CFGetRetainCount(classARC2)) // 참조 카운트가 1감소하여 2가 됩니다.
classARC2 = nil // 참조 카운트가 1 감소하여 더 이상 참조하는 곳이 없으모르 deinit이 실행됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 클래스 인스턴스를 여러 곳에서 참조하게 되면, 원래 인스턴스를 해제하더라도 참조 카운트가 남아있어 deinit이 실행되지 않습니다. 참조되는 모든 값들을 해제해줘야 비로소 deinit이 실행되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특징 때문에 retain cycle이 발생하기도 하는데요, 해당 코드도 보고 가겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623167812494&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ratain cycle
class StrongRefClassA {
    var classB: StrongRefClassB?
    deinit {
        print(&quot;ClassA 할당 해제&quot;)
    }
}

class StrongRefClassB {
    var classA: StrongRefClassA?
    deinit {
        print(&quot;ClassB 할당 해제&quot;)
    }
}

var classA: StrongRefClassA? = StrongRefClassA()
var classB: StrongRefClassB? = StrongRefClassB()

print(CFGetRetainCount(classA)) // reference count = 2(기본값)
print(CFGetRetainCount(classB)) // reference count = 2(기본값)

classA?.classB = classB
classB?.classA = classA

print(CFGetRetainCount(classA)) // reference count = 3
print(CFGetRetainCount(classB)) // reference count = 3

classA = nil
print(CFGetRetainCount(classB?.classA)) // reference count = 2(기본값)
classB = nil // &amp;lt;- 더 이상 classA, classB의 데이터에 접근 할 수 없지만 deinit 실행되지 않았음 -&amp;gt; 메모리 누수 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 더 이상 classA, classB에 접근할 방법이 없는데도 불구하고 참조 카운트가 0이 되지 않아 deinit이 실행되지 않았습니다. 이렇게 되면 메모리 누수가 발생하게 됩니다. 이는 weak, unowned 참조를 사용하면 해결할 수 있는데요, 여기서는 간단하게 코드로만 보고 넘어가겠습니다. ARC에 대해 자세한 내용은 &lt;a href=&quot;https://icksw.tistory.com/204&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;pre id=&quot;code_1623168198880&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class StrongRefClassA {
    weak var classB: StrongRefClassB?
    deinit {
        print(&quot;ClassA 할당 해제&quot;)
    }
}

class StrongRefClassB {
    weak var classA: StrongRefClassA?
    deinit {
        print(&quot;ClassB 할당 해제&quot;)
    }
}

var classA: StrongRefClassA? = StrongRefClassA()
var classB: StrongRefClassB? = StrongRefClassB()

print(CFGetRetainCount(classA)) // reference count = 2(기본값)
print(CFGetRetainCount(classB)) // reference count = 2(기본값)

classA?.classB = classB
classB?.classA = classA

print(CFGetRetainCount(classA)) // reference count = 2(기본값)
print(CFGetRetainCount(classB)) // reference count = 2(기본값) -&amp;gt; 약한 참조를 사용했기 때문에 참조카운트 증가하지 않음

classA = nil // deinit 실행됨
classB = nil // deinit 실행됨 -&amp;gt; 인스턴스가 모두 메모리 해제됨 -&amp;gt; 메모리 누수 발생하지 않음!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 weak 참조를 사용하면 retain cycle을 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 Swift 공식 문서에서 언급되는 내용입니다. 하지만 이러한 특징들 때문에 구조체와 클래스는 메모리에 저장되는 위치가 다른데요, 구조체는 언제 생기고, 사라질지 컴파일 단계에서 알 수 있기 때문에 메모리의 stack 공간에 할당되고, 클래스는 참조가 어디서 어떻게 될지 모르기 때문에 Heap이라는 공간에 할당되게 됩니다.&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;h1&gt;Stack 할당&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack은 LIFO(Last In First Out) 형태의 자료구조로 가장 마지막에 들어간 객체가 가장 먼저 나오게 되는 자료구조인데요, 자료구조 특성상 하나의 명령어로 메모리를 할당, 해제할 수 있습니다. 또한 컴파일 단계에서 언제 생성되고 해제되는지 알 수 있는 구조체와 같은 값들이 스택에 저장되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드들은 각각 독립적인 Stack 공간을 가지고 있기 때문에 상호 배제를 위한 자원이 필요하지 않습니다. 즉 스레드로부터 안전하다는 말이 됩니다. 이러한 특징 때문에 Stack의 값을 사용하는 것이 Heap의 값을 사용하는 것보다 빠르다고 할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;Heap 할당&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap에는 컴파일 단계에서 생성과 해제를 알 수 없는 참조 타입의 객체가 할당됩니다. 즉 Swift에서는 클래스 객체가 힙에 할당되게 됩니다. Heap은 Stack보다 관리하기가 어려운데요, 이는 메모리 할당과 해제가 하나의 명령어로 처리되지 않기 때문입니다. 아까 Stack에서는 pop, push라는 하나의 명령어로 할당, 해제가 이루어졌지만 Heap은 참조 계산도 해줘야 하므로 Stack보다 복잡합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Heap은 스레드가 공유하는 메모리 공간이기 때문에 스레드로부터 안전하지 않습니다. 즉 이를 관리해주기 위한 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;이렇게 Stack, Heap 할당의 차이점과 Swift의 Class, Struct 값들이 어디에 저장되는지 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 코딩을 하다 보면 클래스와 구조체를 혼합해서 쓰는 경우가 있지 않나요? 예를 들어 클래스 안에 구조체 필드가 있다거나, 구조체 안에 클래스 필드가 있다거나... 하는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 나누게 되면 결국 2가지 경우로 나눌 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 타입을 포함하는 참조 타입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단하게 Class 안에 Struct 프로퍼티가 존재하는 경우입니다.&lt;/li&gt;
&lt;li&gt;이 경우 참조 타입이 할당 해제되기 전에 값 타입도 할당 해제되지 않게 하기 위해서 값 타입도 힙에 저장합니다.&lt;/li&gt;
&lt;li&gt;위의 경우 말고도 Swift에서는 클로저 내부에서 사용하는 값 타입도 위의 경우에 해당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;참조 타입을 포함하는 값 타입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Struct 안에 Class 프로퍼티가 존재하는 경우입니다.&lt;/li&gt;
&lt;li&gt;이 경우 값 타입은 힙에 할당되지 않지만 내부에 참조 타입이 있기 때문에 참조 카운트를 처리해줘야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;값 타입의 Copy-on-assignment, Copy-on-write&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 타입을 다른 변수에 할당하면 복사를 하게 됩니다. 즉 새로운 메모리 공간에 같은 값을 복사하게 되는데요, 이를 copy-on-assignment라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 다르게 Copy-on-write는 다른 변수에 할당하면 일단은 메모리를 할당하지 않고 같은 곳을 봅니다. 그러다 해당 값을 변경할 때 실제로 메모리에 값을 복사하고 값을 변경하게 됩니다. 이는 메모리를 최적화해주기 위함이며 Swift에서는 Int, Double, String, Array, Set, Dictionary에서만 사용하고 있습니다. 참조 타입을 포함하고 있는 값 타입은 이러한 메모리 최적화를 할 수 없습니다. 물론 억지로 만들 순 있지만 이는 많은 오버헤드를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 언제 클래스를 쓰고 언제 구조체를 쓰는 것이 좋을까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순한 데이터 값을 보유하기 위해서는 구조체가 낫습니다.&lt;/li&gt;
&lt;li&gt;메모리의 스택은 크기가 크지 않기 때문에 작은 값을 갖는 데이터를 처리할 때 구조체를 사용합니다.&lt;/li&gt;
&lt;li&gt;Objectice-C와 상호 운용성을 원할 때는 클래스를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Swift의 클래스와 구조체에 대해서 조금 더 깊게 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 면접에서 이 질문을 묻는 것은 단순히 swift에서의 사용할 때의 차이뿐만 아니라 CS적인 지식도 함께 묻는 것이고 차이점을 확실하게 알아야 성능 개선도 할 수 있는 개발자라고 판단할 것 같습니다.&lt;/p&gt;
&lt;p 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;</description>
      <category>Swift/Swift_Memo</category>
      <category>Class</category>
      <category>IOS</category>
      <category>struct</category>
      <category>Swift</category>
      <category>면접</category>
      <category>차이점</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/256</guid>
      <comments>https://icksw.tistory.com/256#entry256comment</comments>
      <pubDate>Wed, 9 Jun 2021 01:44:51 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Memento Pattern (메멘토) - 디자인 패턴 공부 19</title>
      <link>https://icksw.tistory.com/255</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다! &lt;/p&gt;
&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://icksw.tistory.com/254&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Mediator Pattern(중재자)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Memento Pattern(메멘토)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;메멘토 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Memento Pattern은 구현의 세부 사항을 공개하지 않고, 즉 캡슐화를 위반하지 않고 객체의 이전 상태를 저장하고 복원할 수 있는 디자인 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_DA3DCADB02BA-1.jpeg&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btQUzv/btq6BapBrht/xiRONucniTvf8P9Vp5Dmj1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btQUzv/btq6BapBrht/xiRONucniTvf8P9Vp5Dmj1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btQUzv/btq6BapBrht/xiRONucniTvf8P9Vp5Dmj1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtQUzv%2Fbtq6BapBrht%2FxiRONucniTvf8P9Vp5Dmj1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1352&quot; height=&quot;363&quot; data-filename=&quot;IMG_DA3DCADB02BA-1.jpeg&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Originator
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자체적으로 현재의 state를 저장하는&amp;nbsp;Memento 객체를 생성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;State를 복원하기 위해 Memento를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Memento
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Originator의 state에 대한 스냅샷 역할을 하는 객체입니다.&lt;/li&gt;
&lt;li&gt;Originator 이외 객체의 접근으로부터 보호합니다.&lt;/li&gt;
&lt;li&gt;Memento를 immutable하게 만들고 생성자를 통해 데이터를 한 번만 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Care Taker
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Memento 객체들을 저장하여 Originator의 동작을 추적합니다.&lt;/li&gt;
&lt;li&gt;Memento의 내용을 조작하거나 검토하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;메멘토 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;Memento Pattern에서 State를 기록하는 Originator 객체가 State를 기록하고 이를 Memento 객체에 저장합니다. Memento 객체는 Originator 객체를 제외하고는 접근 할 수 없도록 만듭니다. 이러한 Memento 객체들은 Caretaker라는 객체에 저장되며 Caretaker는 제한된 인터페이스를 통해서 Memento 객체를 다루기 때문에 Memento 객체의 내부를 조작할 수 없습니다. 하지만 Originator는 Memento 객체에 자유롭게 접근할 수 있기 때문에 원하는 State로 복원할 수 있게 됩니다.&lt;/p&gt;
&lt;h1&gt;메멘토 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화를 위반하지 않고 객체의 state 스냅샷을 생성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Caretaker가 Originator의 State 기록을 유지하므로 Originator의 코드가 단순화됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 Memento 객체를 너무 많이 생성하면 메모리가 많이 사용됩니다.&lt;/li&gt;
&lt;li&gt;Caretaker는 오래된 memento 객체를 삭제 할 수 있도록 Originator의 생명 주기를 추적해야 합니다. 즉 자원을 소비해야 합니다.&lt;/li&gt;
&lt;li&gt;몇몇 언어에서는 Originator만 Memento 객체에 접근할 수 있도록 만드는 것이 어려울 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Memento Pattern을 Swift 언어로 간단하게 구현해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 가지 게임에서 Save, Load 기능이 있는데, 게임을 save 하고 load 하는 기능을 메멘토 패턴으로 구현하기에 재밌어 보여서 한 번 구현해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Originator 역할을 하는 Game 클래스를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622968976938&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Originator
class Game {
    var level: Int = 0
    var score: Int = 0
    
    func setLevel(level: Int) {
        self.level = level
    }
    
    func setScore(score: Int) {
        self.score = score
    }
    
    func makeSnapshot() -&amp;gt; SaveData {
        print(&quot;Level : \(self.level), Score: \(self.score) 상태를 저장합니다.\n&quot;)
        return SaveData(originator: self)
    }
    
    func printCurrentState() {
        print(&quot;현재 상태 Level : \(self.level), Score: \(self.score)&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 정의한 Game의 객체 상태를 저장할 Memento 클래스를 하나 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622969022835&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Memento
class SaveData {
    var originator: Game
    var level: Int = 0
    var score: Int = 0
    
    init(originator: Game) {
        self.originator = originator
        self.level = originator.level
        self.score = originator.score
    }
    
    func load() {
        self.originator.setLevel(level: self.level)
        self.originator.setScore(score: self.score)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Memento 객체 역할을 하는 SaveData 클래스에는 load라는 메서드를 만들어 해당 memento 객체의 상태로 originator의 상태를 복원할 수 있도록 만들었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Caretaker 클래스를 정의해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622969100909&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Caretaker
class GameDataSystem {
    private var history: [SaveData] = []
    
    func save(snapshot: SaveData) {
        self.history.append(snapshot)
    }
    
    func load() {
        if let snapshot = self.history.popLast() {
            print(&quot;최근 저장 상태를 불러옵니다.\n&quot;)
            snapshot.load()
        } else {
            print(&quot;저장 기록이 없습니다.\n&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Caretaker 객체에는 Originator가 만든 Memento 객체들을 저장하고 복원을 위한 load 메서드를 정의해줍니다.&lt;/p&gt;
&lt;p 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;774&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lz4Ve/btq6APGc9is/VV3I9MzpSDFWJoiml5SVf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lz4Ve/btq6APGc9is/VV3I9MzpSDFWJoiml5SVf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lz4Ve/btq6APGc9is/VV3I9MzpSDFWJoiml5SVf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLz4Ve%2Fbtq6APGc9is%2FVV3I9MzpSDFWJoiml5SVf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;599&quot; height=&quot;486&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0VUHJ/btq6CBf9V0l/VqzejMJX9H1xj9EV8zd1b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0VUHJ/btq6CBf9V0l/VqzejMJX9H1xj9EV8zd1b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0VUHJ/btq6CBf9V0l/VqzejMJX9H1xj9EV8zd1b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0VUHJ%2Fbtq6CBf9V0l%2FVqzejMJX9H1xj9EV8zd1b1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;248&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;248&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;이렇게 Memento Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Memento%20Pattern/Memento%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>Memento</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/255</guid>
      <comments>https://icksw.tistory.com/255#entry255comment</comments>
      <pubDate>Sun, 6 Jun 2021 17:51:23 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Mediator Pattern (중재자) - 디자인 패턴 공부 18</title>
      <link>https://icksw.tistory.com/254</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/253&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Iterator Pattern에 대해 알아봤는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Mediator Pattern(중재자)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;중재자 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mediator Pattern은 객체 간 종속성을 줄일 수 있는 패턴입니다. 객체 간 직접 통신을 제한하고 Mediator 객체를 통해서만 통신을 가능하게 하여 상호 작용을 독립적으로 만들도록 하는 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byr0sy/btq52UVKa7Y/br5w5lMJXFWM2Mxu7XARm1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byr0sy/btq52UVKa7Y/br5w5lMJXFWM2Mxu7XARm1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byr0sy/btq52UVKa7Y/br5w5lMJXFWM2Mxu7XARm1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyr0sy%2Fbtq52UVKa7Y%2Fbr5w5lMJXFWM2Mxu7XARm1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;798&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mediator
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Colleague 객체와 통신하기 위한 인터페이스를 정의합니다.&lt;/li&gt;
&lt;li&gt;일반적으로 하나의 알람 메서드만 정의하여 Colleague와의 통신 방법을 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Mediator
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Colleague 객체와 함께 작동할 행동들을 구현합니다.&lt;/li&gt;
&lt;li&gt;존재하는 Colleague 객체들을 참조하고 생명주기를 관리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Colleague Class
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 Colleague 객체들은 Mediator 객체를 참조하고 있습니다.&lt;/li&gt;
&lt;li&gt;각각의 Colleague 객체들은 다른 Colleague와 통신을 할 때 마다 Mediator와 통신합니다.&lt;/li&gt;
&lt;li&gt;각각의 Colleague 객체들은 다른 Colleague를 인식하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;중재자 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 객체에서 발생한 이벤트에 대하여 다른 객체가 조치를 취하고 또 해당 객체가 다른 객체에게 영향을 미치는 추가적인 이벤트를 생성할 때 Mediator Pattern을 사용하면 좋습니다. 즉 객체들을 서로 독립적으로 만들기 위해 객체 간 통신을 막고 통신은 오로지 Mediator 객체를 통해서 가능하도록 만들면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 유저 프로필을 만들고 편집할 수 있는 UI가 존재한다고 해볼게요. 해당 UI는 TextField, Button 등 다양한 뷰들로 구성될거에요. 그러한 뷰들 중 몇몇 뷰들은 다른 뷰들과도 상호작용을 할 수도 있겠죠? 예를 들어 나는 애완동물을 가지고 있어요 라는 체크박스를 체크하면 애완동물의 종류를 작성하는 TextField가 보인다거나 하는 방식으로 말이에요. 이때 체크박스를 체크하면 TextField가 활성화되어야 하는데, 이러한 과정을 체크박스에 이런 기능을 넣게 되면, 해당 체크박스 클래스는 다른 용도로는 사용하지 못하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이러한 객체간 통신을 Mediator 객체가 수행하도록 하여 이런 문제를 해결할 수 있어요. 즉 체크박스에 체크를 했다는 이벤트를 Mediator에 전달하고 Mediator가 거기에 반응해야 하는 TextField에 이 사실을 전달하여 활성화되게 만드는 것입니다. 이렇게 하면 각각의 뷰들의 종속성이 완화되고 다양한 곳에서 재사용할 수 있게 됩니다.&lt;/p&gt;
&lt;h1&gt;중재자 패턴의 결과?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Single Responsibility Principle : 다양한 객체간 통신을 하나의 객체가 수행하도록 만들어 코드를 이해하기 쉽고 유지보수도 쉬워집니다.&lt;/li&gt;
&lt;li&gt;Open / Closed Principle : 객체들을 변경하지 않고도 새로운 Mediator를 도입할 수 있습니다.&lt;/li&gt;
&lt;li&gt;프로그램의 다양한 구성 요소간 결합도를 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;개별 객체들을 더 쉽게 재사용 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간이 지남에 따라 Mediator 객체가 God Object가 될 수 있습니다. 이 말은 상호작용을 모두 Mediator 객체가 수행하기 때문에 Mediator 객체가 복잡해지고 나중엔 유지하기 어려운 단일체가 될 수 있다는 말이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 간단하게 Mediator Pattern을 Swift로 구현해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 본 유저 프로필을 편집하는 UI 예제를 구현해보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 Mediator 프로토콜과 Base Colleague 클래스를 하나 만들어 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이벤트를 구분하기 위한 열거형도 하나 만들어줄게요.&lt;/p&gt;
&lt;pre id=&quot;code_1622369292511&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Mediator
protocol Mediator {
    func notify(sender: Colleague, event: EventType)
}

enum EventType {
    case checkBoxSelect
    case checkBoxUnselect
}

// Base Colleague
class Colleague {
    var mediator: Mediator?
    
    func setMediator(mediator: Mediator) {
        self.mediator = mediator
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 뒤 Colleague의 자식 클래스를 몇 개 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 필요한 것은 CheckBox, TextField 클래스일 거 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1622369337791&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Collegue
class CheckBox: Colleague {
    var isSelect: Bool = false {
        didSet {
            if isSelect {
                print(&quot;CheckBox 선택&quot;)
            } else {
                print(&quot;CheckBox 선택 해제&quot;)
            }
        }
    }
    
    func checkBoxClick() {
        self.isSelect = !self.isSelect
        if self.isSelect {
            self.mediator?.notify(sender: self, event: .checkBoxSelect)
        } else {
            self.mediator?.notify(sender: self, event: .checkBoxUnselect)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1622369360561&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Collegue
class TextField: Colleague {
    var isHidden: Bool = true {
        didSet {
            if isHidden {
                print(&quot;TextField 비활성화&quot;)
            } else {
                print(&quot;TextField 활성화&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;이렇게 Colleague까지 만들어주셨다면 이들의 통신을 중재할 Mediator 객체를 하나 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622369393941&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Mediator
class ProfileUI: Mediator {
    var checkBox: CheckBox
    var textField: TextField
    
    init(checkBox: CheckBox, textField: TextField) {
        self.checkBox = checkBox
        self.textField = textField
        
        self.checkBox.setMediator(mediator: self)
        self.textField.setMediator(mediator: self)
    }
    
    func notify(sender: Colleague, event: EventType) {
        switch event {
        case .checkBoxSelect:
            self.textField.isHidden = false
        case .checkBoxUnselect:
            self.textField.isHidden = true
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 만들어주면 간단하게 Mediator Pattern 구현을 다 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mediator에 해당하는 ProfileUI 클래스를 잘 보시면, notify 메서드, 즉 알림이 오게 되면 해당 알림의 이벤트 타입에 따라 특정 이벤트를 처리해주는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p 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;566&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWjMLg/btq54n30FVW/rb6ce4rxJKw7faR2zgAm80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWjMLg/btq54n30FVW/rb6ce4rxJKw7faR2zgAm80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWjMLg/btq54n30FVW/rb6ce4rxJKw7faR2zgAm80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWjMLg%2Fbtq54n30FVW%2Frb6ce4rxJKw7faR2zgAm80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;239&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckBox에는 TextField와 상호작용을 위한 아무런 코드가 없음에도 불구하고 위와 같이 checkBoxClick() 메서드를 실행하게 되면 Mediator에 의해 TextField에도 어떤 작업이 수행되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Mediator Pattern을 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Mediator%20Pattern/Mediator%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>Mediator</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/254</guid>
      <comments>https://icksw.tistory.com/254#entry254comment</comments>
      <pubDate>Sun, 30 May 2021 19:14:03 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Iterator Pattern (이터레이터) - 디자인 패턴 공부 17</title>
      <link>https://icksw.tistory.com/253</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/252&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Interpreter Pattern(인터프리터)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴인 Iterator Pattern(이터레이터)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;이터레이터 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Iterator Pattern은 list, stack, tree 등과 같은 기본적인 표현을 노출하지 않고 객체를 순차적으로 접근할 수 있는 방법을 제공하는 패턴입니다. Swift에서는 IteratorProtocol을 채택하여 for문을 사용하여 반복할 수 있는 타입을 정의하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;659&quot; width=&quot;713&quot; height=&quot;353&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S1YgK/btq6aPLOn7A/hSjnb00zvoo0RekqLRqkB0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S1YgK/btq6aPLOn7A/hSjnb00zvoo0RekqLRqkB0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S1YgK/btq6aPLOn7A/hSjnb00zvoo0RekqLRqkB0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS1YgK%2Fbtq6aPLOn7A%2FhSjnb00zvoo0RekqLRqkB0%2Fimg.jpg&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;659&quot; width=&quot;713&quot; height=&quot;353&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Iterator
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요소에 접근하고 탐색하기 위한 인터페이스를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Iterator
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Iterator 인터페이스를 구현합니다.&lt;/li&gt;
&lt;li&gt;Iterator 객체는 자체적으로 반복의 진행 상황을 추적해야합니다. 이를 통해 여러 개의 반복들이 서로 독립적으로 동일한 컬렉션을 반복할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Iterable Collection
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Iterator 객체를 생성하기 위한 인터페이스를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Collection
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적절한 Concrete Iterator의 인스턴스를 반환하는 Iterator 생성 인터페이스를 구현합니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 요청 할 때마다 Concrete Iterator 인스턴스를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스를 통해 Collection, Iterator 모두 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;이터레이터 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체들의 그룹을 유지하는 타입이 있고 이를 반복문에서 사용하고 싶을 때 iterator pattern을 사용합니다. Collection은 프로그래밍을 할 때 가장 많이 사용하는 데이터 타입 중 하나입니다. 이러한 Collection은 사실 그냥 하나의 컨테이너일 뿐인데요, 다른 코드에서 Collection의 요소들을 사용할 수 있도록 접근하는 방법을 제공하지 않으면 그냥 데이터 덩어리에 불과하게 되겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collection의 모든 요소에 접근하는 방법을 제공하기 위해 다양한 방법을 구현할 수 있을 텐데요, 만약 트리라는 Collection이 존재한다면, DFS, BFS과 같은 알고리즘을 Collection에 추가하는 것은 데이터를 저장한다는 책임을 갖는 Collection의 입장에서는 이상할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이러한 알고리즘은 Iterator라는 별도의 객체로 구현하는 방법이 Iterator 패턴입니다. 알고리즘 자체를 구현하는 것 외에도 Iterator 객체는 현재 위치나 끝까지 남은 요소 수와 같은 세부 정보를 캡슐화하여 여러 개의 반복문이 동시에 동작하더라도 서로 독립적으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Iterator는 Collection의 요소를 가지고오는 하나의 방법을 제공하는데요, 클라이언트는 아무것도 반환하지 않을 때까지 반복문을 통해 요소를 가지고 오게 됩니다. 또한 특정 Collection을 탐색하는 특별한 방법이 필요한 경우 Collection, Client를 수정하지 않고 새로운 Iterator 클래스만 만들어주면 됩니다.&lt;/p&gt;
&lt;h1&gt;이터레이터 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Single Responsibility Principle : 순회 알고리즘을 별도의 클래스로 추출하여 단일 책임 원칙을 지킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;Open / Closed Principle : 새로운 타입의 컬렉션 및 Iterator를 구현하더라도 기존 코드에서 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;각각의 Iterator 객체에는 자체적인 반복 상태가 존재하기 때문에 동일한 컬렉션을 병렬로 처리할 수도 있습니다.&lt;/li&gt;
&lt;li&gt;각각의 Iterator 객체에는 자체적인 반복 상태가 존재하기 때문에 반복을 지연하거나 다시 실행할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 단순한 Collection만 필요로 하는 경우 Iterator Pattern은 굳이 필요 없을 수도 있습니다.&lt;/li&gt;
&lt;li&gt;Iterator를 사용하는 것은 몇몇 Collection의 요소를 직접 처리하는 것 보다 비효율적일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Iterator Pattern에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다!&lt;/p&gt;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>iterator</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/253</guid>
      <comments>https://icksw.tistory.com/253#entry253comment</comments>
      <pubDate>Sun, 30 May 2021 16:44:52 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Interpreter Pattern (인터프리터) - 디자인 패턴 공부 16</title>
      <link>https://icksw.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다.  &lt;/p&gt;
&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://icksw.tistory.com/251&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Command Pattern(커맨드)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴인 Interpreter Pattern(인터프리터)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;인터프리터 패턴이란?&lt;/h1&gt;
&lt;p data-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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;820&quot; data-filename=&quot;IMG_84A7D5959E84-1.jpeg&quot; width=&quot;669&quot; height=&quot;414&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rn4bm/btq52iI4XGU/KisRZkGZ2sCoH3B6lVEKvK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rn4bm/btq52iI4XGU/KisRZkGZ2sCoH3B6lVEKvK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rn4bm/btq52iI4XGU/KisRZkGZ2sCoH3B6lVEKvK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frn4bm%2Fbtq52iI4XGU%2FKisRZkGZ2sCoH3B6lVEKvK%2Fimg.jpg&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;820&quot; data-filename=&quot;IMG_84A7D5959E84-1.jpeg&quot; width=&quot;669&quot; height=&quot;414&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Abstract Expression
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Abstract Syntax Tree의 모든 노드에서 사용할 Interpret 작업을 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Terminal Expression
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널 기호와 관련된 작업을 구현합니다.&lt;/li&gt;
&lt;li&gt;문장의 모든 터미널 기호에 인스턴스가 필요합니다.&lt;/li&gt;
&lt;li&gt;여기서 말하는 터미널 기호라고 함은 어떤 문장에서 의미있는 최소의 단위입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Nonterminal Expression
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 클래스는 문법이 가진 모든 규칙을 필요로합니다. (규칙은 R1... Rn으로 표현합니다.)&lt;/li&gt;
&lt;li&gt;R1... Rn Symbol 각각에 대한 AbstractExpression 타입의 인스턴스 변수를 유지합니다.&lt;/li&gt;
&lt;li&gt;Grammar에서 Nonterminal Symbol에 대한 해석 연산을 구현합니다. Interpret는 R1... Rn까지의 규칙을 나타내는 변수를 통해 재귀적 방식으로 해석합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Context
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Interpreter가 해석할 문장입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문법이 정의하는 언어의 특정 문장을 나타내는 Abstract Syntax Tree를 정의합니다. Abstract Syntax Tree는 Nonterminal Expression, Terminal Expression의 인스턴스로 구성됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Interpret 작업을 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;인터프리터 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;예를 들어 SQL 언어의 쿼리문을 해석하는 인터프리터를 만든다고 하면, &quot;Select * From Worlds&quot; 라는 쿼리문을 해석할 인터프리터를 만들어주면 되는 것이죠. 이러한 인터프리터는 쿼리 문의 여러 가지 문법을 가지고 입력받은 쿼리문을 해석하게 되고 해석된 쿼리문을 통해 원하는 DB작업을 할 수 있게 됩니다.&lt;/p&gt;
&lt;p 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;h1&gt;인터프리터 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문법 변경 및 확장이 쉽습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터프리터 패턴은 클래스를 사용하여 문법 규칙을 나타내기 때문에 상속을 사용하여 문법을 변경하거나 확장 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문법을 구현하는 것도 쉽습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Abstract Syntax Tree에서 노드를 정의하는 클래스는 비슷한 구현부를 가지고 있습니다. 따라서 이러한 클래스를 만들기 쉽고 Compiler, Parser를 사용하여 생성을 자동화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 표현을 해석하는 새로운 방법을 쉽게 추가할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 문법은 유지하기 어렵습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터프리터 패턴은 문법의 모든 규칙에 대해 하나 이상의 클래스를 정의하는데요, 따라서 많은 규칙을 갖고 있는 문법은 관리, 유지가 어려울 수 있습니다. 따라서 많이 복잡한 경우에는 컴파일러나 파서를 사용하는 것이 더 적합할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터프리터 Swift 예제 : &lt;a href=&quot;https://github.com/kingreza/Swift-Interpreter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kingreza/Swift-Interpreter&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1622273570741&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;kingreza/Swift-Interpreter&quot; data-og-description=&quot;Design Patterns in Swift: Interpreter . Contribute to kingreza/Swift-Interpreter development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kingreza/Swift-Interpreter&quot; data-og-url=&quot;https://github.com/kingreza/Swift-Interpreter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rdgUU/hyKngQNOT8/IerNsdRRRcXTEQKWWAork0/img.png?width=1200&amp;amp;height=600&amp;amp;face=978_136_1060_226,https://scrap.kakaocdn.net/dn/8UDUa/hyKnhWtjRs/RRDe9QJ16iNFSaK0U7pUN1/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://github.com/kingreza/Swift-Interpreter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kingreza/Swift-Interpreter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rdgUU/hyKngQNOT8/IerNsdRRRcXTEQKWWAork0/img.png?width=1200&amp;amp;height=600&amp;amp;face=978_136_1060_226,https://scrap.kakaocdn.net/dn/8UDUa/hyKnhWtjRs/RRDe9QJ16iNFSaK0U7pUN1/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_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;kingreza/Swift-Interpreter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Design Patterns in Swift: Interpreter . Contribute to kingreza/Swift-Interpreter development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터프리터 패턴은 지금까지 공부한 디자인 패턴 중 가장 어려웠던 패턴이었던거 같습니다.&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>Interpreter</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Swift</category>
      <category>인터프리터</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/252</guid>
      <comments>https://icksw.tistory.com/252#entry252comment</comments>
      <pubDate>Sat, 29 May 2021 16:33:45 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Command Pattern (커맨드) - 디자인 패턴 공부 15</title>
      <link>https://icksw.tistory.com/251</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/250&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 행동 패턴 중 하나인 Chain of Responsibility Pattern(책임 연쇄 패턴)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴인 Command Pattern(커맨드)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;커맨드 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Command Pattern은 요청을 하나의 객체로 캡슐화해서 이를 큐나 log로 처리하는 방법입니다. 이렇게 캡슐화한 작업은 실행 취소를 할 수도 있게 만들 수 있어요. OS에서 스케줄러가 작업들을 관리하거나, iOS에서 Operating Queue에 Operation 객체로 작업을 추가하는 예 들이 떠오르는 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1259&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bekm4y/btq5FWZQZ2p/skqOtlDSAPFzWCbvwzJ29k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bekm4y/btq5FWZQZ2p/skqOtlDSAPFzWCbvwzJ29k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bekm4y/btq5FWZQZ2p/skqOtlDSAPFzWCbvwzJ29k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbekm4y%2Fbtq5FWZQZ2p%2FskqOtlDSAPFzWCbvwzJ29k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1259&quot; height=&quot;740&quot; data-origin-width=&quot;1259&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Command
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업을 실행하기 위한 인터페이스를 정의합니다.&lt;/li&gt;
&lt;li&gt;Command를 실행하기 위한 하나의 메서드로 이루어진 경우가 많습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Command
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Receiver 객체와 작업 사이 바인딩을 정의합니다.&lt;/li&gt;
&lt;li&gt;Receiver에서 해당 작업을 호출하여 실행합니다.&lt;/li&gt;
&lt;li&gt;직접 작업을 수행하는 것이 아닌 Receiver에게 전달하기 위한 목적을 갖고 있습니다.&lt;/li&gt;
&lt;li&gt;코드를 단순화 하기 위해 클래스를 합칠 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Concrete Command 객체를 만들고 Receiver를 설정합니다.&lt;/li&gt;
&lt;li&gt;Receiver의 인스턴스를 포함한 작업에 필요한 모든 매개변수를 Command의 생성자에 전달하여 작업을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Invoker
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Invoker 클래스에는 명령 객체에 대한 참조를 저장하기 위한 필드가 있어야 합니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 생성자를 통해 Invoker 객체를 만들 때 Command 객체를 받게 됩니다.&lt;/li&gt;
&lt;li&gt;요청을 Receiver에게 직접 보내는 것이 아닌 해당 요청의 시작을 담당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Receiver
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웬만한 클래스는 Receiver 역할을 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;요청된 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;커맨드 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 패턴은 작업을 캡슐화해서 Receiver에게 보내서 처리하고 싶을 때 사용하면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 컴퓨터를 할 때 자주 사용하는 복사, 붙여 넣기를 생각해볼게요. 어떤 메모장 앱에 복사, 붙여넣기 기능을 만들고 싶어서 버튼을 만들었습니다. 버튼 클래스를 만들고 버튼에 복사, 붙여넣기을 수행하는 메서드를 만들었죠. 하지만 사람들은 Ctrl + C, Ctrl + V와 같은 단축키로도 해당 기능을 쓰고 싶고, 다른 버튼에도 복사, 붙여 넣기 기능이 있었으면 좋겠다고 문의가 들어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&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;복사, 붙여 넣기를 처리해주는 Command 객체를 만드는 거죠. 버튼은 그냥 눌러지면 해당 명령을 실행하게만 합니다. 이렇게 하면 여러개의 복사, 붙여넣기 기능을 하는 버튼을 만들어도 Command 객체는 하나만 존재하게 되고, 이는 코드의 중복을 막아줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 다른 기능을 추가하고 싶다면 Command 객체만 더 만들어내면 됩니다. 그런 뒤 버튼을 눌렀을 때 해당 Command 객체만 전달하고 실행되도록 만들기만 하면 됩니다.&lt;/p&gt;
&lt;h1&gt;커맨드 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Single Responsibility Principle (단일 책임 원칙)을 만족합니다. Command 객체를 통해 작업을 수행하는 객체와 작업을 호출하는 객체를 나눌 수 있습니다.&lt;/li&gt;
&lt;li&gt;Open / Closed Principle (개방 / 폐쇄 원칙)을 만족합니다. 클라이언트의 코드를 수정하지 않고도 새로운 Command를 추가할 수 있죠&lt;/li&gt;
&lt;li&gt;실행 취소, 다시 실행을 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;작업의 시작을 지연시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;여러 개의 단순한 명령을 조합해서 복잡한 명령을 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Receiver, Invoker 사이에 관계를 도입해야 하므로 코드가 복잡해질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Command Pattern을 Swift로 구현해보겠습니다.&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;가장 먼저 Command 프로토콜을 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621868237995&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Command
protocol Command {
    var receiver: TextEditor { get set }
    var backup: String { get set }
    
    func execute()
    func undo()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Command를 실행도 하고 취소도 할 수 있게 만들기 위해 메서드를 2개 만들어줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Command 객체는 receiver를 참조해야 한다고 했으니, 그런 필드도 하나 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Invoker를 만들어보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621868337189&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Invoker
class Invoker {
    var history: [Command] = []
    
    private func push(command: Command) {
        self.history.append(command)
    }
    
    private func pop() -&amp;gt; Command? {
        return history.popLast()
    }
    
    func executeCommand(command: Command) {
        command.execute()
        self.push(command: command)
    }
    
    func undoCommand() {
        let command = self.pop()
        if command == nil {
            print(&quot;되돌릴 작업이 없습니다.&quot;)
        } else {
            command?.undo()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Invoker는 Command를 수행하고, 되돌리는 걸 시작하는 역할만 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 실행 취소를 구현하기 위해 실행된 Command들을 순서대로 저장할 Stack을 하나 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 history라고 이름을 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Receiver는 Command가 실제로 수행되는 곳으로 아래와 같이 만들어줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621868378735&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Receiver
class TextEditor {
    var text: String = &quot;&quot;
    var clipboard: String = &quot;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Command 들만 구현하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 복사, 붙여 넣기, 쓰기만 구현했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621868434063&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Command
class CopyCommand: Command {
    var receiver: TextEditor
    var backup: String = &quot;&quot;
    
    init(receiver: TextEditor) {
        self.receiver = receiver
    }
    
    func undo() {
        receiver.clipboard = self.backup
        self.backup = &quot;&quot;
    }

    func execute() {
        self.backup = receiver.clipboard
        receiver.clipboard = receiver.text
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1621868447157&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Command
class PasteCommand: Command {
    var receiver: TextEditor
    var backup: String
    
    init(receiver: TextEditor) {
        self.receiver = receiver
        self.backup = self.receiver.clipboard
    }
    
    func undo() {
        let startIndex = receiver.text.startIndex
        let lastIndex = receiver.text.index(startIndex, offsetBy: receiver.text.count - backup.count)
        receiver.text = String(receiver.text[startIndex..&amp;lt;lastIndex])
    }
    
    func execute() {
        self.receiver.text += backup
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1621868461434&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Command
class WriteCommand: Command {
    var receiver: TextEditor
    var backup: String
    
    init(receiver: TextEditor, backup: String) {
        self.receiver = receiver
        self.backup = backup
    }
   
    func undo() {
        let startIndex = receiver.text.startIndex
        let lastIndex = receiver.text.index(startIndex, offsetBy: receiver.text.count - backup.count)
        receiver.text = String(receiver.text[startIndex..&amp;lt;lastIndex])
    }
    
    func execute() {
        receiver.text += backup
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 건 모두 만들었으니 이제 실행해보겠습니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZqra/btq5CBh9n00/e2nkQ6TVGKL0wF5XPOczb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZqra/btq5CBh9n00/e2nkQ6TVGKL0wF5XPOczb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZqra/btq5CBh9n00/e2nkQ6TVGKL0wF5XPOczb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZqra%2Fbtq5CBh9n00%2Fe2nkQ6TVGKL0wF5XPOczb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;472&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결과를 보면 실행과 취소가 잘 수행되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Command가 실행되면 receiver에서 실제로 실행되고, invoker에서 undo를 하면 가장 최근에 한 작업이 취소되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&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;이렇게 Command Pattern을 공부하고 실제로 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에 나온 개념을 최대한 활용하여 구현했는데, Invoker의 역할, Receiver의 역할, Command들의 생성 위치 등이 많이 고민됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Command%20Pattern/Command%20Pattern&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Swift/Design_Pattern</category>
      <category>behavioral</category>
      <category>command</category>
      <category>design</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/251</guid>
      <comments>https://icksw.tistory.com/251#entry251comment</comments>
      <pubDate>Tue, 25 May 2021 00:11:51 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Chain of Responsibility Pattern (책임 연쇄) - 디자인 패턴 공부 14</title>
      <link>https://icksw.tistory.com/250</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 Behavioral Patterhn(행동 패턴)에 대해 간단하게 알아봤는데요, 이번 글에서부터는 행동 패턴의 여러 가지 패턴을 하나씩 자세히 알아보도록 하겠습니다. 이번 글에서 알아볼 행동 패턴은 Chain Responsibility Pattern(책임 연쇄)입니다.&lt;/p&gt;
&lt;h1&gt;책임 연쇄 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chain Responsibility Pattern은 Handler 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;즉 여러 개의 핸들러가 연결되어있고, 이렇게 연결된 핸들러들을 Chain이라고 합니다. 어떤 요청이 Chain에 들어왔을 때 이를 처리 할 수도 있고 다음 핸들러로 넘길 수도 있는 것이죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_D484A46E29DF-1.jpeg&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B3hZM/btq5uIHNj81/Jn8SDibAcVpsGtJh0MSDzK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B3hZM/btq5uIHNj81/Jn8SDibAcVpsGtJh0MSDzK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B3hZM/btq5uIHNj81/Jn8SDibAcVpsGtJh0MSDzK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB3hZM%2Fbtq5uIHNj81%2FJn8SDibAcVpsGtJh0MSDzK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;412&quot; data-filename=&quot;IMG_D484A46E29DF-1.jpeg&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Handler
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트의 요청을 처리하기 위한 인터페이스를 정의합니다.&lt;/li&gt;
&lt;li&gt;현재 핸들러에서 처리하지 않을것이라면 요청을 넘길 다음 핸들러를 구현합니다. (처리할 수 없는 요청인 경우 마지막 핸들러까지 가서도 처리되지 않을 수 있습니다. 마지막 핸들러는 다음 핸들러를 갖지 않을 것이므로 이는 선택적으로 만드시면 됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Handler
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;다음 핸들러에 작업을 넘깁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chain의 첫 번째 Concrete Handler 객체에 요청을 합니다. (이를 요청하기 위해 클라이언트가 첫 번째 Concrete Handler에 접근할 수 있어야 합니다.)&lt;/li&gt;
&lt;li&gt;클라이언트의 요청은 Chain에 존재하는 모든 핸들러로 전송될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;책임 연쇄 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;h1&gt;책임 연쇄 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 처리 순서를 제어할 수 있습니다.&lt;/li&gt;
&lt;li&gt;결합도를 감소해줍니다. 체인에 존재하는 객체들은 요청을 처리할 것인지 넘길 것인지만 판단하면 되고, 체인의 다른 객체들이 뭘 하는지 알 필요가 없습니다. 물론 이건 클라이언트도 몰라도 됩니다.&lt;/li&gt;
&lt;li&gt;Single Responsibility Principle(단일 책임 원칙)을 지킬 수 있습니다. 작업을 수행하는 클래스, 작업을 호출하는 클래스를 분리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Open, Closed Principle(개방/폐쇄 원칙)을 지킬 수 있습니다. 기존 클라이언트의 코드를 바꾸지 않고 새로운 핸들러를 앱에 추가할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리되지 않는 요청이 있을 수 있지만, 요청할 때는 이걸 알 수 없습니다. 체인의 끝까지 가야 알 수 있기 때문이죠.&lt;/li&gt;
&lt;li&gt;체인을 잘 못 만들 경우 사이클이 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 책임 연쇄 패턴을 Swift 코드로 구현해보겠습니다.&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;일단 가장 먼저 Handler 프로토콜을 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621762525805&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Handler
protocol CustomerServiceHandler: class {
    var nextHandler: CustomerServiceHandler? { get set }
    func setNext(handler: CustomerServiceHandler)
    func handle(request: String) -&amp;gt; String
}

extension CustomerServiceHandler {
    func setNext(handler: CustomerServiceHandler) {
        if self.nextHandler == nil {
            self.nextHandler = handler
        } else {
            self.nextHandler?.setNext(handler: handler)
        }
    }
}&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;그런 뒤 여러 가지 request에 따른 작업을 처리할 수 있는 Concrete Handler들을 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621762942334&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Handler
class MainAppleServiceHandler: CustomerServiceHandler {
    var nextHandler: CustomerServiceHandler?
    
    func handle(request: String) -&amp;gt; String {
        if request == &quot;Apple&quot; {
            return &quot;Apple 부서로 연결합니다.&quot;
        } else {
            if let response = nextHandler?.handle(request: request) {
                return response
            } else {
                return &quot;원하시는 서비스는 제공하지 않습니다.&quot;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1621762955679&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Handler
class MobileServiceHandler: CustomerServiceHandler {
    var nextHandler: CustomerServiceHandler?
    
    func handle(request: String) -&amp;gt; String {
        if request == &quot;Mobile&quot; {
            return &quot;Mobile 부서로 연결합니다.&quot;
        } else {
            if let response = nextHandler?.handle(request: request) {
                return response
            } else {
                return &quot;원하시는 서비스는 제공하지 않습니다.&quot;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1621762963869&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Handler
class IPhoneServiceHandler: CustomerServiceHandler {
    var nextHandler: CustomerServiceHandler?
    
    func handle(request: String) -&amp;gt; String {
        if request == &quot;iPhone&quot; {
            return &quot;iPhone 부서로 연결합니다.&quot;
        } else {
            if let response = nextHandler?.handle(request: request) {
                return response
            } else {
                return &quot;원하시는 서비스는 제공하지 않습니다.&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;이렇게 여러 개의 Handler 객체를 만들었다면 이젠 Chain 가지고 있을 Client를 만들어줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621764336435&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Client
class Client {
    private var firstHandler: CustomerServiceHandler?
    
    func request(request: String) -&amp;gt; String {
        return self.firstHandler?.handle(request: request) ?? &quot;firstHandler를 설정해주세요&quot;
    }
    func addHandler(handler: CustomerServiceHandler) {
        if let firstHandler = firstHandler {
            firstHandler.setNext(handler: handler)
        } else {
            self.firstHandler = handler
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Client는 Chain의 첫 번째 Handler만 가지고 있으면 됩니다.&lt;/p&gt;
&lt;p 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;766&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dE2HdS/btq5toJ3fQM/jBhIK2h1poYQ78XYez5sjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dE2HdS/btq5toJ3fQM/jBhIK2h1poYQ78XYez5sjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dE2HdS/btq5toJ3fQM/jBhIK2h1poYQ78XYez5sjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdE2HdS%2Fbtq5toJ3fQM%2FjBhIK2h1poYQ78XYez5sjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;431&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;638&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;근데 iPad 서비스도 추가하고 싶다면 어떻게 하면 될까요?&lt;/p&gt;
&lt;pre id=&quot;code_1621764560739&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Concrete Handler
class IPadeServiceHandler: CustomerServiceHandler {
    var nextHandler: CustomerServiceHandler?
    
    func handle(request: String) -&amp;gt; String {
        if request == &quot;iPad&quot; {
            return &quot;iPad 부서로 연결합니다.&quot;
        } else {
            if let response = nextHandler?.handle(request: request) {
                return response
            } else {
                return &quot;원하시는 서비스는 제공하지 않습니다.&quot;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 iPad Handler 클래스를 만들어 준 뒤 Client에 추가해주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rvn0Y/btq5vNWI2Pc/JEoyfZGupDspHelwQ3LKbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rvn0Y/btq5vNWI2Pc/JEoyfZGupDspHelwQ3LKbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rvn0Y/btq5vNWI2Pc/JEoyfZGupDspHelwQ3LKbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frvn0Y%2Fbtq5vNWI2Pc%2FJEoyfZGupDspHelwQ3LKbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;559&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;778&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;공부를 하며 한 가지 들었던 의문은 하나의 핸들러 클래스가 여러 개의 next Handler를 가지면 책임 연쇄 패턴이 아닌 건지 였습니다.&amp;nbsp;지금은 Linked List의 느낌으로 요청을 처리하는데, 뭔가 Tree 느낌으로 요청을 처리할 수도 있지 않을까가 궁금했습니다. 이러한 구현은 Handler 프로토콜을 채택한 객체를 배열로 처리하면 가능할 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 저는 Chain에 Handler를 추가할 때 항상 체인의 마지막 부분에 추가해줬는데, 이렇게 추가하는 게 맞는지도 의문이었습니다. 특정 Handler 객체에 setNext()로 다음 핸들러만 설정하도록 처음엔 구현했는데, 그렇게 하면 의도하지 않게 체인에 존재하는 핸들러를 잃어버릴 수도 있고 체인을 구성하는 게 상당히 귀찮아져서 항상 마지막 부분에 추가해줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음.. 뭔가 직접 구현을 해보며 생각할 것이 많았던 패턴이었던 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다 ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Behavioral%20Pattern/Chain%20of%20Responsibility/Chain%20of%20Responsibility&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;</description>
      <category>Swift/Design_Pattern</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/250</guid>
      <comments>https://icksw.tistory.com/250#entry250comment</comments>
      <pubDate>Sun, 23 May 2021 19:22:35 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Behavioral Pattern (행동 패턴) - 디자인 패턴 공부  13.5</title>
      <link>https://icksw.tistory.com/249</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서 알아본 Proxy Pattern을 마무리로 GoF에 나오는 디자인 패턴 중 Structual Pattern(구조 패턴)에 대한 공부를 마치고 이번 글부터는 Behavioral Pattern(행동 패턴)에 대해 알아보도록 하겠습니다. 이번 글에서는 다양한 행동 패턴에 대해 알아보기 전에 행동 패턴이 무엇인지 간단하게 알아보고 넘어가도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;Behavioral Pattern (행동 패턴) 이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글까지 알아본 구조 패턴은 클래스와 객체가 어떻게 구성되는지와 관련이 있었는데요, 행동 패턴은 알고리즘과 객체 간 책임 할당과 관련이 있습니다. 행동 패턴은 객체와 클래스의 패턴뿐만 아니고 객체, 클래스 간에 의사소통 패턴도 설명하는데요, 이러한 패턴은 런타임에서 처리하기 어려운 복잡한 control flow들이라는 특징을 갖습니다. 이렇게 행동 패턴을 사용해서 개발자가 control flow보다는 객체가 연결되는 방식에 집중할 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GoF에 등장하는 행동 패턴은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chain of Responsibility&lt;/li&gt;
&lt;li&gt;Command&lt;/li&gt;
&lt;li&gt;Interpreter&lt;/li&gt;
&lt;li&gt;Iterator&lt;/li&gt;
&lt;li&gt;Mediator&lt;/li&gt;
&lt;li&gt;Memento&lt;/li&gt;
&lt;li&gt;Observer&lt;/li&gt;
&lt;li&gt;State&lt;/li&gt;
&lt;li&gt;Strategy&lt;/li&gt;
&lt;li&gt;Template Method&lt;/li&gt;
&lt;li&gt;Visitor&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 행동 패턴들은 상속을 사용하여 클래스 간 동작을 정의하는 Behavioral Class Pattern, 상속보다는 객체 구성을 사용하는 Behavioral Object Pattern이 존재합니다. 다음 글부터 11개의 행동 패턴들을 자세히 살펴보도록 하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>Swift/Design_Pattern</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/249</guid>
      <comments>https://icksw.tistory.com/249#entry249comment</comments>
      <pubDate>Sun, 23 May 2021 16:29:10 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Proxy Pattern (프록시) - 디자인 패턴 공부 13</title>
      <link>https://icksw.tistory.com/248</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/247&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 구조 패턴 Flyweight Pattern에 대해 알아봤는데요, 이번 글에서는 GoF의 마지막 구조 패턴인 Proxy Pattern(프록시)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;프록시 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 패턴은 다른 객체에 대한 접근을 제어할 수 있도록 surrogate, placeholder를 제공할 수 있는 구조 패턴입니다. 프록시가 원본 객체에 대한 접근을 제어하기 때문에 어떤 요청이 원본 객체에 전달되기 전이나 후에 작업을 수행할 수 있어요.&lt;/p&gt;
&lt;p 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-filename=&quot;IMG_774F3E310D2C-1.jpeg&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDpag/btq5uI1z3xV/4zfcSZJ67YFM3w5YolTCWk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDpag/btq5uI1z3xV/4zfcSZJ67YFM3w5YolTCWk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDpag/btq5uI1z3xV/4zfcSZJ67YFM3w5YolTCWk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDpag%2Fbtq5uI1z3xV%2F4zfcSZJ67YFM3w5YolTCWk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;348&quot; data-filename=&quot;IMG_774F3E310D2C-1.jpeg&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subject (Service Interface)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스의 인터페이스를 정의합니다. 프록시는 서비스 객체로 사용 할 수 있도록 인터페이스를 준수해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Real Subject (Service)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스는 비즈니스 로직을 제공하는 클래스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Proxy
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 객체에는 서비스 객체를 참조하는 필드가 있습니다.&lt;/li&gt;
&lt;li&gt;Lazy initialization, logging, access control, caching 등과 같은 작업을 완료 한 뒤 클라이언트의 요청을 서비스 객체에 전달합니다.&lt;/li&gt;
&lt;li&gt;참조하는 서비스 객체와 동일한 인터페이스를 준수합니다.&lt;/li&gt;
&lt;li&gt;프록시에는 Remote Proxy, Virtual Proxy, Protection Proxy가 있으며 각각의 책임으로 구분합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Remote Proxy : 요청을 처리하고 서비스 객체에 이를 전달하는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;Virtual Proxy : 서비스 객체에 대한 정보를 캐싱하여 접근을 연기합니다.&lt;/li&gt;
&lt;li&gt;Protection Proxy : 특정 작업을 요청한 객체가 해당 작업을 수행할 권한을 가지고 있는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 객체가 필요한 모든 코드를 프록시에 전달하기 위해 동일한 인터페이스를 사용하는 서비스와 프록시 객체와 함께 작동해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;프록시 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;h1&gt;프록시 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 알지 못하는 상태에서 서비스 객체를 제어할 수 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 서비스 객체의 생명 주기를 신경 쓰지 않을 때 이를 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;프록시는 서비스 객체가 준비되지 않았거나 사용할 수 없는 경우에도 작동합니다.&lt;/li&gt;
&lt;li&gt;서비스나 클라이언트를 변경하지 않고 새로운 프록시를 도입할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 클래스를 도입해야 하므로 코드가 복잡해집니다.&lt;/li&gt;
&lt;li&gt;응답이 지연될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Proxy Pattern을 Swift로 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브 비디오 예제를 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브를 보면 영상 목록이 있고 마우스를 올리면 영상이 프리뷰 형태로 재생되는데요, 영상 목록을 구성할 때는 영상 정보가 없어도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프리뷰를 보여주기 위해서는 영상 정보가 존재해야겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 영상 목록만 보여줄 때는 Proxy 객체가 처리하고 마우스를 올려서 프리뷰를 보려고 할 때 영상 객체를 생성해서 보여주면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 영상 이름을 바꾸는 기능을 Proxy 객체가 클라이언트의 권한에 따라 제어하는 것도 한 번 만들어 볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Subject(Service) 프로토콜을 먼저 만들어 볼게요.&lt;/p&gt;
&lt;pre id=&quot;code_1621681251362&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Subject (Service Interface)
protocol YoutubeService {
    func showPreview()
    func setName(name: String)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리뷰를 보여주는 기능과 이름을 수정하는 기능을 구현하라고 정의했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 실제 비디오 클래스를 만들어 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621681281669&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Real Service
class YoutubeVideo: YoutubeService {
    
    private var id: Int
    private var name: String
    
    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
    
    func showPreview() {
        print(&quot;\(name) Preview Play  \n&quot;)
    }
    
    func setName(name: String) {
        print(&quot;\(self.name)에서 \(name)으로 이름 수정 완료\n&quot;)
        self.name = name
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 정의한 프로토콜을 채택해서 간단하게 만들 수 있어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Proxy 클래스를 만들어야 하는데, Proxy 클래스를 만들기 전에 Client를 먼저 간단하게 만들어보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621681472257&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Client
class Client {
    var auth: AccessAuth
    
    init(auth: AccessAuth) {
        self.auth = auth
    }
}

enum AccessAuth {
    case owner
    case guest
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 권한만 열거형으로 구분할 수 있는 Client 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이젠 Proxy 클래스를 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy 클래스도 아까 정의한 프로토콜을 채택해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621681365099&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Proxy
class YoutubeVideoProxy: YoutubeService {
    
    private var youtubeVideo: YoutubeService?
    
    private var id: Int
    private var name: String
    private var client: Client
    
    init(id: Int, name: String, client: Client) {
        self.id = id
        self.name = name
        self.client = client
    }
    
    func showTitle() {
        print(&quot;\(id), \(name) Youtube Video&quot;)
    }
    
    func showPreview() {
        if let youtubeVideo = self.youtubeVideo {
            youtubeVideo.showPreview()
        } else {
            loadVideoInfo()
            self.youtubeVideo?.showPreview()
        }
    }
    
    private func loadVideoInfo() {
        if let _ = self.youtubeVideo {
            print(&quot;Cache File Exist!\n&quot;)
        } else {
            print(&quot;load Video... &quot;)
            self.youtubeVideo = YoutubeVideo(id: self.id, name: self.name)
        }
        
    }
    
    func setName(name: String) {
        if client.auth == .guest {
            print(&quot;Guest는 이름 수정 불가\n&quot;)
        } else if client.auth == .owner {
            self.loadVideoInfo()
            self.name = name
            self.youtubeVideo?.setName(name: name)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy객체에서는 실제 비디오 객체가 없더라도 보여줄 수 있는 showTitle() 메서드와 비디오 객체가 필요할 때 로드할 수 있는 loadVideoInfo() 메서드가 추가적으로 구현되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비디오 객체가 필요 없을 때는 Proxy가 자체적으로 처리하다가 비디오 객체가 반드시 필요한 부분에서는 비디오 객체를 로드하고 이를 캐시에 저장하여 사용하는 방식으로 구현했습니다.&lt;/p&gt;
&lt;p 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;650&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnq1OG/btq5txtscH2/FMaAbZIXVlRjhF15vHGbY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnq1OG/btq5txtscH2/FMaAbZIXVlRjhF15vHGbY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnq1OG/btq5txtscH2/FMaAbZIXVlRjhF15vHGbY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnq1OG%2Fbtq5txtscH2%2FFMaAbZIXVlRjhF15vHGbY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;251&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 보시면 지금은 owner 권한이라 이름도 바꿀 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;showTitle()은 프록시 객체가 혼자서 처리할 수 있는 작업이라 비디오를 로드하지 않았지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;showPreview()와 setName(name:) 메서드는 비디오 객체가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 showPreview()를 수행하며 비디오를 캐시에 저장했기 때문에 setName(name:)을 했을 땐 캐싱해서 사용한 것도 볼 수 있어요.&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;635&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH1mfP/btq5vD6HvtH/YrrVQE2GmrQVR3tBEifTy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH1mfP/btq5vD6HvtH/YrrVQE2GmrQVR3tBEifTy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH1mfP/btq5vD6HvtH/YrrVQE2GmrQVR3tBEifTy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH1mfP%2Fbtq5vD6HvtH%2FYrrVQE2GmrQVR3tBEifTy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;164&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위와 같이 guest 권한으로 이름을 변경하려고 하면 Proxy에서 막히는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&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;이렇게 구조 패턴 중 하나인 Proxy Pattern에 대해 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 공부한 패턴 중 가장 어려웠던 패턴이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 이해하려 했지만 제가 잘 이해한 건지도 잘 모르겠네요.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Structural%20Pattern/Proxy%20Pattern/Proxy%20Pattern&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;Proxy Pattern을 마지막으로 GoF 책에서 나오는 모든 구조 패턴을 공부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글부터는 GoF 책의 Behavior Pattern에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>OOP</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/248</guid>
      <comments>https://icksw.tistory.com/248#entry248comment</comments>
      <pubDate>Sat, 22 May 2021 20:16:56 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Flyweight Pattern (플라이웨이트) - 디자인 패턴 공부 12</title>
      <link>https://icksw.tistory.com/247</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&gt;
&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://icksw.tistory.com/246&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 구조 패턴 중 Facade Pattern(퍼사드)에 대해 알아봤는데요, 이번 글에서는 계속해서 구조 패턴 중 하나인 Flyweight Pattern(플라이 웨이트)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;플라이 웨이트 패턴이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyweight&amp;nbsp;패턴은 메모리 사용량과 처리를 최소화하기 위한 디자인 패턴입니다. Flyweight&amp;nbsp;패턴은 각 객체의 모든 데이터를 유지하는 대신 여러 객체 간에 state 공통부분을 공유하여 메모리에 더 많은 객체를 넣을 수 있는 구조적 디자인 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyweight&amp;nbsp;패턴은 모든 객체가 기본 데이터를 공유하여 메모리를 절약하게 되는데요, 데이터 공유를 위해 이러한 객체들은 보통 &lt;b&gt;immutable&lt;/b&gt;(변경 불가능) 합니다. 플라이 웨이트 패턴에는 Flyweight라는 객체와 이를 반환하는 static 메서드가 존재합니다. 어디선가 본 것 같은 느낌이 드신다면 예전에 알아본 생성 패턴의 &lt;a href=&quot;https://icksw.tistory.com/239&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Singleton 패턴&lt;/a&gt;을 변형한 패턴으로 볼 수도 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_ADFCE64B5E54-1.jpeg&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CK1wE/btq5vesb3CZ/oidV0KmWDDj5hfIbUwkjC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CK1wE/btq5vesb3CZ/oidV0KmWDDj5hfIbUwkjC0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CK1wE/btq5vesb3CZ/oidV0KmWDDj5hfIbUwkjC0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCK1wE%2Fbtq5vesb3CZ%2FoidV0KmWDDj5hfIbUwkjC0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;318&quot; data-filename=&quot;IMG_ADFCE64B5E54-1.jpeg&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyweight
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유할 수 있는 정보를 갖는 플라이 웨이트 객체를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flyweight Factory
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyweight&amp;nbsp;객체를 만들고 관리합니다.&lt;/li&gt;
&lt;li&gt;Flyweight의 공유 정보가 올바르게 공유되도록 합니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 Flyweight&amp;nbsp;객체를 요청하면 팩토리가 이전에 만들어 놓은 동일한 Flyweight 객체가 있는지 찾아보고 없다면 새로 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concrete Flyweight
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyweight&amp;nbsp;인터페이스를 구현하고 공유 상태에 대한 저장공간을 확보합니다.&lt;/li&gt;
&lt;li&gt;여기에 저장하는 상태들은 &lt;b&gt;intrinsic state(고유한 상태)&lt;/b&gt;라고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Unshared Concrete Flyweight (Context)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 클래스는 Concrete Flyweight 객체를 자식으로 갖습니다.&lt;/li&gt;
&lt;li&gt;모든 Flyweight&amp;nbsp;서브 클래스를 공유할 필요는 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyweight에 대한 참조를 유지합니다.&lt;/li&gt;
&lt;li&gt;Flyweight&amp;nbsp;객체의 각각의 상태를 처리하거나 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;플라이 웨이트&amp;nbsp;패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;예를 들어 FPS 게임에서 총을 쏠 때 나오는 총알을 생각해보겠습니다. 총알을 만약 각각 하나의 객체로 모두 독립적으로 생성한다면, 많은 총알이 발사될 때 메모리를 많이 차지해서 성능이 나빠질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 수많은 총알은 위치만 달라질 뿐 서로 가지고 있는 특징들은 모두 같습니다. 총알의 색, 크기 등은 모두 같은데 이를 모든 총알 객체가 독립적으로 가지고 있어야 할까요? 이럴 때 플라이 웨이트 패턴을 사용하여 메모리를 절약할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 예시로 든 총알 객체의 경우 위치, 속도는 각각의 총알이 모두 각자 가지고 있는 고유한 정보입니다. 이러한 정보를&amp;nbsp;&lt;b&gt;intrinsic state&lt;/b&gt;라고 합니다. 그리고 총알의 색, 크기와 같은 모두 같은 정보를 &lt;b&gt;extrinsic state&lt;/b&gt;라고 합니다. &lt;b&gt;intrinsic state&lt;/b&gt;(속도, 좌표 등)는 각각의 총알 객체만 수정할 수 있고 &lt;b&gt;extrinsic state&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;즉 Flyweight&amp;nbsp;패턴은 &lt;b&gt;extrinsic state&lt;/b&gt;를 각각의 객체에 저장하는 것이 아닌 외부에 공유할 수 있는 상태로 저장하여 메모리를 절약하는 거예요. Flyweight&amp;nbsp;객체에 공유할 수 있는 정보를 저장하여 객체에서는 이를 가져다 쓰게 됩니다. 이 경우 동일한 플라이 웨이트 객체를 서로 다른 객체가 사용할 수 있기 때문에 플라이 웨이트 객체는 &lt;b&gt;immutable&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;이러한 Flyweight&amp;nbsp;객체를 쉽게 만들기 위해 팩토리 메서드 패턴을 사용합니다. 찾고자 하는 Flyweight 객체를 찾고 있다면 이를 반환해주고 없다면 새로 만들어서 반환해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;플라이 웨이트 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱에 유사한 객체가 많다면 메모리를 절약할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyweight 메서드를 호출할 때마다 존재하는 데이터 검색 등 런타임 비용이 발생할 수 있습니다. 이는 메모리 절약으로 발생하는 문제이므로 상쇄되긴 하지만 Flyweight 객체가 많이 존재하게 될수록 비용이 증가합니다.&lt;/li&gt;
&lt;li&gt;코드가 복잡해집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Flyweight Pattern을 Swift로 구현해보겠습니다.&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;가장 먼저 Flyweight 클래스와 이를 갖는 Bullet 클래스를 만들어야 할 것 같아요.&lt;/p&gt;
&lt;pre id=&quot;code_1621613258479&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Flyweight
class BulletFlyweight {
    let color: String
    let size: Float
    
    init(color: String, size: Float) {
        self.color = color
        self.size = size
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyweight 객체의 값은 한 번 만들어지면 변경 할 수 없게 let으로 선언했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621613614973&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Unshared Concrete Flyweight
class Bullet {
    var x: Float
    var y: Float
    var velocity: Float
    private var extrinsicState: BulletFlyweight
    
    init(x: Float, y: Float, velocity: Float, extrinsicState: BulletFlyweight) {
        self.x = x
        self.y = y
        self.velocity = velocity
        self.extrinsicState = extrinsicState
    }
    
    func getState() {
        print(&quot;Flyweight : \(x),\(y),\(velocity) 값을 갖는 \(extrinsicState.color), \(extrinsicState.size) 총알&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 extrinsicState에는 접근할 수 없도록 만들어줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 클라이언트가 Bullet을 만들 때 필요한 Flyweight 객체를 반환해줄 FlyweightFactory 클래스도 만들어보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621613662754&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Flyweight Factory
class BulletFlyweightFactory {
    static private var bulletFlyweightList: [BulletFlyweight] = []
    static func getBulletFlyweight(color: String, size: Float) -&amp;gt; BulletFlyweight {
        if let flyweightIndex = self.bulletFlyweightList.firstIndex(where: { (bullet) -&amp;gt; Bool in
            return bullet.color == color &amp;amp;&amp;amp; bullet.size == size
        }) {
            return self.bulletFlyweightList[flyweightIndex]
        } else {
            self.bulletFlyweightList.append(BulletFlyweight(color: color, size: size))
            print(&quot;\(color),\(size) Flyweight 객체 생성&quot;)
            return self.bulletFlyweightList.last ?? BulletFlyweight(color: color, size: size)
        }
    }
    static var flyweightCount: Int {
        return self.bulletFlyweightList.count
    }
    private init() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 보면 요구하는 Flyweight 객체가 이미 만들어 놓은 객체인지 검색하고 만약 만들어놓았다면 해당 객체를 반환하고 그렇지 않다면 새로 만들어서 리스트에 추가한 뒤 반환합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u72Ad/btq5tbpVrjx/hOltFZsvEOhDBpUZuH2Skk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u72Ad/btq5tbpVrjx/hOltFZsvEOhDBpUZuH2Skk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u72Ad/btq5tbpVrjx/hOltFZsvEOhDBpUZuH2Skk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu72Ad%2Fbtq5tbpVrjx%2FhOltFZsvEOhDBpUZuH2Skk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;310&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결과를 보면 각각 동일한 Flyweight 객체를 공유하는 5개의 총알을 만드는 코드인데요, Flyweight 객체는 2번만 만드는 것을 볼 수 있습니다. 즉 10번을 만들지도 않고 10개의 데이터를 유지하는 것이 아닌 2개의 데이터만 만들고 이를 공유하는 것이죠. 이런 식으로 메모리를 절약할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구조 패턴 중 하나인 Flyweight Pattern에 대해 알아보고 간단하게 구현도 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Structural%20Pattern/Flyweight%20Pattern/Flyweight%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/247</guid>
      <comments>https://icksw.tistory.com/247#entry247comment</comments>
      <pubDate>Sat, 22 May 2021 01:34:42 +0900</pubDate>
    </item>
    <item>
      <title>[Swift 디자인 패턴] Facade Pattern (퍼사드) -  디자인 패턴 공부 11</title>
      <link>https://icksw.tistory.com/246</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다.  &lt;/p&gt;
&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://icksw.tistory.com/244&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 구조 패턴 중 Decorator Pattern(데코레이터)에 대해 알아봤는데요, 이번 글에서는 계속해서 구조 패턴 중 하나인 Facade Pattern(퍼사드)에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;퍼사드 패턴이란?&lt;/h1&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-filename=&quot;IMG_EF87634C08EC-1.jpeg&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;829&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFyL8P/btq5qptzHcf/C8X8KkDCUW3o0kCppUazyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFyL8P/btq5qptzHcf/C8X8KkDCUW3o0kCppUazyk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFyL8P/btq5qptzHcf/C8X8KkDCUW3o0kCppUazyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFyL8P%2Fbtq5qptzHcf%2FC8X8KkDCUW3o0kCppUazyk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;397&quot; data-filename=&quot;IMG_EF87634C08EC-1.jpeg&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;829&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Facade
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 Subsystem 클래스가 클라이언트의 요청에 응답해야 하는지 알고 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트의 요청을 적절한 Subsystem에게 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Subsystem
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subsystem 기능을 구현합니다.&lt;/li&gt;
&lt;li&gt;Facade 객체에서 전달받은 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;서브 시스템 클래스들은 Facade 객체의 존재를 모릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 서브 시스템 객체를 직접 호출하는 대신 Facade를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;퍼사드 패턴은 언제 쓰나요?&lt;/h1&gt;
&lt;p data-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;정리하면 아래와 같은 상황에서 Facade 패턴을 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브 시스템을 계층화하고 싶을 때&lt;/li&gt;
&lt;li&gt;복잡한 서브 시스템들을 간단하게 사용하기 위한 인터페이스를 만들고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;퍼사드 패턴의 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브 시스템의 복잡성으로부터 코드를 분리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;서브 시스템으로부터 클라이언트를 보호하고 클라이언트가 서브 시스템을 사용하기 쉽게 만들어줍니다.&lt;/li&gt;
&lt;li&gt;Facade를 사용하여 시스템과 객체 간 종속성을 계층화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;컴파일 종속성을 줄여 서브 시스템이 변경될 때 컴파일 시간을 줄여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Facade는 앱의 모든 클래스에 결합된 객체가 될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 Facade Pattern을 Swift로 구현해보겠습니다.&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;쇼핑몰에는 Customer, Product 객체가 존재합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621521969056&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Customer {
    let identifier: String
    var name: String
    var address: String
}

extension Customer: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
    static func == (lhs: Customer, rhs: Customer) -&amp;gt; Bool {
        return lhs.identifier == rhs.identifier
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1621521976931&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Product {
    let identifier: String
    var name: String
    var cost: Int
}

extension Product: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
    static func == (lhs: Product, rhs: Product) -&amp;gt; Bool {
        return lhs.identifier == rhs.identifier
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 쇼핑몰 시스템에는 재고 관리를 위한 기능과 손님들의 주문 현황을 알기 위한 기능이 존재할 거예요.&lt;/p&gt;
&lt;pre id=&quot;code_1621522681525&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 재고 관리
class AvailableProduct {
    var productList: [Product: Int] = [:]
    
    init(productList: [Product: Int]) {
        self.productList = productList
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1621522695271&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 주문 현황
class OrderList {
    var orderList: [Customer: [Product]] = [:]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 이러한 4개의 서브 시스템들을 가지고 주문을 위한 Facade를 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문하는 로직은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;손님의 이름과 손님이 주문한 상품의 이름을 출력합니다.&lt;/li&gt;
&lt;li&gt;AvailableProduct 객체에 해당 상품의 재고가 존재하는지 확인합니다.&lt;/li&gt;
&lt;li&gt;만약 재고가 없다면 재고가 없다고 출력합니다.&lt;/li&gt;
&lt;li&gt;재고가 있다면 재고를 한 개 줄이고 OrderList에 해당 정보를 추가합니다.&lt;/li&gt;
&lt;li&gt;주문이 완료되었다고 출력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 Facade로 구현하면 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1621522863832&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OrderFacade {
    let productDB: AvailableProduct
    let orderDB: OrderList
    
    init(availableProduct: AvailableProduct, orderList: OrderList) {
        self.productDB = availableProduct
        self.orderDB = orderList
    }
    
    func order(product: Product, customer: Customer) {
        print(&quot;\(customer.name)님이 \(product.name)를 주문하셨습니다.&quot;)
        if let count = self.productDB.productList[product] {
            if count == 0 {
                print(&quot;\(product.name) 재고가 없습니다.&quot;)
            } else if count &amp;gt; 0 {
                self.productDB.productList[product] = count - 1
                
                var orderList = self.orderDB.orderList[customer, default: []]
                orderList.append(product)
                self.orderDB.orderList[customer] = orderList
                print(&quot;\(customer.name)님의 \(product.name)를 주문 접수 완료!&quot;)
            }
        } else {
            print(&quot;존재하지 않는 제품입니다.&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 실제로 사용해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEQDd5/btq5pC7OiBb/XllMYenvggRKe2qa3BjlG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEQDd5/btq5pC7OiBb/XllMYenvggRKe2qa3BjlG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEQDd5/btq5pC7OiBb/XllMYenvggRKe2qa3BjlG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEQDd5%2Fbtq5pC7OiBb%2FXllMYenvggRKe2qa3BjlG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1348&quot; height=&quot;692&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 상품들과 손님 객체를 만들어 Facade 객체를 만듭니다. 그런 뒤 Facade 객체의 메서드를 사용하여 손쉽게 물건을 주문하는 것을 볼 수 있어요. 이렇게 다양한 서브 시스템들을 쉽게 사용할 수 있도록 만들어주는 것이 Facade Pattern이라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구조 패턴 중 하나인 Facade Pattern에 대해 알아보고 간단하게 구현도 해봤습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/iDevPingu/Swift_Design_Pattern_Study/tree/main/Structural%20Pattern/Facade%20Pattern/Facade%20Pattern&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;</description>
      <category>Swift/Design_Pattern</category>
      <category>design</category>
      <category>Fa&amp;ccedil;ade</category>
      <category>IOS</category>
      <category>pattern</category>
      <category>Swift</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/246</guid>
      <comments>https://icksw.tistory.com/246#entry246comment</comments>
      <pubDate>Fri, 21 May 2021 00:15:35 +0900</pubDate>
    </item>
    <item>
      <title>[iOS 앱개발] iOS 앱의 상태변화에 따른 AppDelegate, SceneDelegate 메서드</title>
      <link>https://icksw.tistory.com/245</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Pingu입니다. &lt;/p&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;a href=&quot;https://icksw.tistory.com/189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;앱의 생명주기&lt;/a&gt;에 따라 호출되는 AppDelegate 메서드들을 알아볼 계획입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 13 이후부터는 SceneDelegate가 생겨나서 AppDelegate가 하는 일들을 대체하는 메서드들도 생겨났는데요.&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; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYTwF%2FbtqM5e6BMzU%2FTik7o3GHJGK1KmmrkCGcyk%2Fimg.jpg&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;438&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNRi4/btq5hkfkL1k/P7tWzLiOussKuktTHa2K20/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNRi4/btq5hkfkL1k/P7tWzLiOussKuktTHa2K20/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNRi4/btq5hkfkL1k/P7tWzLiOussKuktTHa2K20/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYTwF%2FbtqM5e6BMzU%2FTik7o3GHJGK1KmmrkCGcyk%2Fimg.jpg&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;438&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Scene을 사용하게 되면 AppDelegate의 메서드들이 호출되는 것이 아닌 SceneDelegate의 메서드들이 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SceneDelegate에 대해서 더 자세히 알고 싶으시면&amp;nbsp;&lt;a href=&quot;https://icksw.tistory.com/137&quot;&gt;여기&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 Scene이라는 개념이 등장하며 나온 오래된 자료라 지금과는 메서드 이름도 조금 다르지만 크게 다른 것은 없는 거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 앱의 생명주기에 따라 호출되는 AppDelegate 메서드들을 하나씩 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;applicationDidBecomeActive(UIApplication), sceneDidBecomeActive(UIScene)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 Active 상태가 되었을 때 호출되는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit은 이 메서드를 호출해서 앱이 In-Active -&amp;gt; Active 상태로 변한 것을 알 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit은 이 메서드가 호출되면 didBecomeActiveNotification이라는 Notification을 post 해서 앱 전체에서 해당 이벤트를 처리할 수 있도록 해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;applicationWillResignActive(UIApplication), sceneWillResignActive(UIScene)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 Active 상태를 포기했을 때 호출되는 메서드입니다. (Active -&amp;gt; In-Active)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit은 이 메서드를 호출하여 앱이 Active -&amp;gt; In-Active 상태로 변한 것을 알 수 있습니다. 홈 화면으로 나가거나 제어 센터, 알림 센터를 사용할 때 In-Active 상태가 되므로 호출됩니다. 앱에 저장되지 않은 사용자 데이터가 있다면 저장하게 됩니다. 하지만 이러한 방법보다는 특정 시점에 사용자 데이터를 저장하는 것이 좋다고 해요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIKit은 이 메서드를 호출 한 뒤 willResignActiveNotification이라는 Notification을 post 해서 앱 전체에서 해당 이벤트를 처리할 수 있도록 해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;applicationDidEnterBackground(UIApplication), sceneDidEnterBackground(UIScene)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 Background 상태가 됐을 때 호출되는 메서드입니다. (In-Active -&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;UIKit은 이 메서드를 호출하여 앱이 In-Active -&amp;gt; Background 상태로 변한 것을 알 수 있습니다. 이렇게 백그라운드 상태가 되면 타이머를 무효화하고 앱 상태 정보를 저장하게 됩니다. applicationDidEnterBackground 메서드는 빠르게 반환되기 때문에 만약 이 메서드에서 수행할 작업이 있고 추가적인 실행시간이 필요하다면 beginBackgroundTask(expirationHandler:) 메서드를 사용하면 됩니다.&lt;/p&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은 이 메서드를 호출 한 뒤 didEnterBackgroundNotification이라는 Notification을 post 해서 앱 전체에서 해당 이벤트를 처리할 수 있도록 해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;applicationWillEnterForeground(UIApplication), sceneWillEnterForeground(UIScene)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 Foreground 상태가 됐을 때 호출되는 메서드입니다. (Background -&amp;gt; Foreground)&lt;/p&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은 이 메서드를 호출하여 Background -&amp;gt; Foreground 상태로 변한 것을 알 수 있습니다.&lt;/p&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은 이 메서드를 호출하기 전에 willEnterForegroundNotification이라는 Notification을 post 해서 앱 전체에서 해당 이벤트를 처리할 수 있도록 해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;applicationWillTerminate(UIApplication)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 종료되기 직전에 호출되는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 SceneDelegate에는 존재하지 않습니다. 이 메서드가 호출된 후 앱이 종료되게 되면 메모리에서 완전히 제거됩니다. 즉 이 메서드를 호출할 때 사용자 데이터를 저장하고 타이머를 무효화하는 등의 작업을 수행해야 합니다.&lt;/p&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은 이 메서드를 호출 한 뒤 willTerminateNotification을 post 해서 앱 전체해서 해당 이벤트를 처리할 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 실제로 메서드들이 언제 어떻게 호출되는지 알아볼게요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 사용 중 홈 화면으로 이동할 때&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;Background.gif&quot; width=&quot;532&quot; height=&quot;336&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctKTpg/btq5i26Apdl/xefLD630scnoZsaZtfrsK0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctKTpg/btq5i26Apdl/xefLD630scnoZsaZtfrsK0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctKTpg/btq5i26Apdl/xefLD630scnoZsaZtfrsK0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ctKTpg/btq5i26Apdl/xefLD630scnoZsaZtfrsK0/img.gif&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;Background.gif&quot; width=&quot;532&quot; height=&quot;336&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;applicationWillRegisnActive -&amp;gt; applicationDidEnterBackground 순으로 호출되는 것을 볼 수 있어요&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;홈 화면에서 앱을 다시 켤 때 (Not run 상태가 아닌 Background 상태의 앱을 키는 경우)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;Foreground.gif&quot; width=&quot;540&quot; height=&quot;341&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxqroJ/btq5bNQSj4H/KzV9SHAHrh3xxCWKUAKKAk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxqroJ/btq5bNQSj4H/KzV9SHAHrh3xxCWKUAKKAk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxqroJ/btq5bNQSj4H/KzV9SHAHrh3xxCWKUAKKAk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cxqroJ/btq5bNQSj4H/KzV9SHAHrh3xxCWKUAKKAk/img.gif&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;Foreground.gif&quot; width=&quot;540&quot; height=&quot;341&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;applicationWillEnterForeground -&amp;gt; applicationDidBecomeActive 순으로 호출 되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 아예 종료된 상태의 앱을 실행할 땐 applicationdDidBecomeActive만 호출됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 사용 중 알람 센터로 이동할 때&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;AlarmCenter.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oTGZH/btq5bM5vAF3/tTkKOslu3o8u1Opq7bek9K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oTGZH/btq5bM5vAF3/tTkKOslu3o8u1Opq7bek9K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oTGZH/btq5bM5vAF3/tTkKOslu3o8u1Opq7bek9K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/oTGZH/btq5bM5vAF3/tTkKOslu3o8u1Opq7bek9K/img.gif&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;AlarmCenter.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알람 센터로 이동 할 땐 이상하게 applicationWillResignActive -&amp;gt; applicationDidBecomeActive -&amp;gt;&amp;nbsp;applicationWillResignActive 순으로 호출되는데 결국 In-Active 상태가 되긴 하지만 불필요하게 두 번 호출되는 부분이 존재했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 사용 중 제어 센터로 이동할 때&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;ControlCenter.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CxtNz/btq5f3rDNr1/koQ3vdN7x9HMWoVGZZNKm1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CxtNz/btq5f3rDNr1/koQ3vdN7x9HMWoVGZZNKm1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CxtNz/btq5f3rDNr1/koQ3vdN7x9HMWoVGZZNKm1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/CxtNz/btq5f3rDNr1/koQ3vdN7x9HMWoVGZZNKm1/img.gif&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;ControlCenter.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;applicationWillResignActive -&amp;gt; applicationDidBecomeActive 순으로 호출됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱을 종료할 때&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;AppTerminate.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHjvC/btq5duDHt8k/eWK8mTKp1MtO01jf9ledk1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHjvC/btq5duDHt8k/eWK8mTKp1MtO01jf9ledk1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHjvC/btq5duDHt8k/eWK8mTKp1MtO01jf9ledk1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kHjvC/btq5duDHt8k/eWK8mTKp1MtO01jf9ledk1/img.gif&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;360&quot; data-filename=&quot;AppTerminate.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 종료하는 방법에는 다양한 방법이 있을 수 있겠지만 위와 같이 앱 사용 중 앱 전환기로 이동한 뒤 앱을 종료하게 되면 applicationWillResignActive -&amp;gt; applicationDidEnterBackground -&amp;gt; applicationWillTerminate 순으로 호출되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 앱의 상태변화에 따라 호출되는 AppDelegate, SceneDelegate 메서드에 대해 알아봤습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드가 호출될 때마다 Notification도 post 되니 다양한 곳에서 활용할 수 있을 거 같아요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>iOS/iOS_Memo</category>
      <category>appdelegate</category>
      <category>IOS</category>
      <category>lifecycle</category>
      <category>SceneDelegate</category>
      <author>Dev_Pingu</author>
      <guid isPermaLink="true">https://icksw.tistory.com/245</guid>
      <comments>https://icksw.tistory.com/245#entry245comment</comments>
      <pubDate>Thu, 20 May 2021 00:28:03 +0900</pubDate>
    </item>
  </channel>
</rss>