<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>글그리 블로그</title>
    <link>https://eastroot1590.tistory.com/</link>
    <description>글그리 블로그</description>
    <language>ko</language>
    <pubDate>Sat, 9 May 2026 01:48:22 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>글그리</managingEditor>
    <item>
      <title>Core Data (1)</title>
      <link>https://eastroot1590.tistory.com/entry/Core-Data-1</link>
      <description>&lt;p&gt;iOS 개발 중 로컬에 데이터를 저장할 때 &lt;code&gt;UserDefault&lt;/code&gt;를 주로 사용했는데, &lt;code&gt;UserDefault&lt;/code&gt;는 Key-Value로 저장되는 데이터라서 비교적 간단한 데이터는 저장해도 괜찮을 것 같지만 데이터베이스 역할을 하기에는 부적절해보였다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CoreData&lt;/code&gt;는 애플에서 제공하는 프레임워크로, 데이터베이스를 만들고 접근할 수 있는 방법을 제공한다. &lt;/p&gt;
&lt;p&gt;아무래도 이론적인 부분을 파고들기 보다는 간단한 예제를 통해 기본적인 흐름을 파악하는게 익숙하기 때문에 간단한 예제를 만들어본 후에 이론적인 부분을 살펴보도록 하겠다.&lt;/p&gt;
&lt;h2&gt;예제 프로젝트&lt;/h2&gt;
&lt;p&gt;먼저 iOS 프로젝트를 생성한다. 여기서 &lt;strong&gt;Use Core Data&lt;/strong&gt;를 체크하면 자동으로 데이터베이스 파일이 만들어지고 기본적인 접근 코드가 &lt;code&gt;AppDelegate&lt;/code&gt;에 생성된다. (물론 체크하지 않고 만들어도 나중에 추가할 수 있다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MM7j0/btrkaOurtGW/IQBY6sOc7biEynsjC51pT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MM7j0/btrkaOurtGW/IQBY6sOc7biEynsjC51pT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MM7j0/btrkaOurtGW/IQBY6sOc7biEynsjC51pT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMM7j0%2FbtrkaOurtGW%2FIQBY6sOc7biEynsjC51pT0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;&lt;code&gt;CoreData&lt;/code&gt;를 테스트하기 위한 프로젝트니까 &lt;strong&gt;Use Core Data&lt;/strong&gt;를 체크 하고 생성하면 &lt;code&gt;AppDelegate&lt;/code&gt; 클래스 안에 기본적인 &lt;code&gt;CoreData&lt;/code&gt;코드가 생성되고 프로젝트 이름과 똑같은 &lt;code&gt;.xcdatamodeld&lt;/code&gt;파일이 생긴 것을 볼 수 있다. 먼저 &lt;code&gt;AppDelegate&lt;/code&gt; 코드를 보면 어떻게 사용하는지 알 수 있을 것 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: &amp;quot;CoreDataTest&amp;quot;)
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError(&amp;quot;Unresolved error \(error), \(error.userInfo)&amp;quot;)
        }
    })
    return container
}()

// MARK: - Core Data Saving support

func saveContext () {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
        } catch {
            let nserror = error as NSError
            fatalError(&amp;quot;Unresolved error \(nserror), \(nserror.userInfo)&amp;quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;persistentContainer&lt;/code&gt;라는 인스턴스를 사용해서 데이터베이스에 접근하고 저장할 수 있는 것 같다. container를 만드는 코드를 보니 프로젝트를 생성할 때 기본적으로 생성된 &lt;code&gt;.xcdatamodeld&lt;/code&gt;파일 이름이랑 똑같은 이름으로 만드는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그럼 이제 이 파일을 열어보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YXNPA/btrklXvIOLE/XpDPDPlLNKP3RSD7ayXoL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YXNPA/btrklXvIOLE/XpDPDPlLNKP3RSD7ayXoL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YXNPA/btrklXvIOLE/XpDPDPlLNKP3RSD7ayXoL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYXNPA%2FbtrklXvIOLE%2FXpDPDPlLNKP3RSD7ayXoL0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;Xcode 버전마다 UI는 다를 수 있지만 위 모습에서 크게 벗어나지는 않을 것이다. 데이터베이스에 접근할 수 있는 프레임워크라고 했으니까 데이터베이스 용어를 사용해서 설명하자면 &lt;strong&gt;Entity&lt;/strong&gt;는 데이터베이스의 &lt;strong&gt;테이블&lt;/strong&gt;이라고 볼 수 있다. 지금은 아무 테이블도 없는 상태이니 테스트에 사용할 테이블을 하나 만들어보자. 우측 하단에 있는 &lt;strong&gt;Add Entity&lt;/strong&gt;버튼을 눌러서 Entity를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y9Uxz/btrkilLflCN/FixqOddPBNZcoZLBVW1JUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y9Uxz/btrkilLflCN/FixqOddPBNZcoZLBVW1JUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y9Uxz/btrkilLflCN/FixqOddPBNZcoZLBVW1JUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy9Uxz%2FbtrkilLflCN%2FFixqOddPBNZcoZLBVW1JUk%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;이름은 &lt;strong&gt;Person&lt;/strong&gt;이라고 지었다. 그리고 오른쪽 페이지에 보이는 &lt;strong&gt;Attribute&lt;/strong&gt;가 바로 데이터베이스의 &lt;strong&gt;Row&lt;/strong&gt;이름이다. 사람 정보를 표현하기 위해 Entity의 이름을 Person이라고 지었으니, 그에 걸맞는 Attribute를 몇 개 추가해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHHfp/btrkinbg6m8/ce72nbEEVzp0MKpznJJq9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHHfp/btrkinbg6m8/ce72nbEEVzp0MKpznJJq9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHHfp/btrkinbg6m8/ce72nbEEVzp0MKpznJJq9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHHfp%2Fbtrkinbg6m8%2Fce72nbEEVzp0MKpznJJq9K%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;Attribute는 기본적인 데이터 타입(&lt;code&gt;Int&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Double&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt;, &lt;code&gt;Bool&lt;/code&gt; 등)을 가질 수 있다. 지금은 이름과 나이만 추가했기 때문에 이런 타입으로도 충분해 보이지만 나중에 더 복잡한 데이터가 필요하게 될 경우에는 &lt;code&gt;Transformable&lt;/code&gt;을 사용해서 커스텀 오브젝트 타입도 사용할 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;일단 빠르게 동작하는 모습을 확인하려고 만드는 예제니까 쉽게 &lt;code&gt;String&lt;/code&gt;이랑 &lt;code&gt;Decimal&lt;/code&gt;만 추가한 후에 &lt;code&gt;ViewController&lt;/code&gt;로 이동해서 실제로 데이터를 만들어서 저장하는 코드를 작성해보자. 먼저 &lt;code&gt;AppDelegate&lt;/code&gt;에 정의된 container에서 context를 얻어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    let context = appDelegate.persistentContainer.viewContext

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt;를 통해 위에서 정의한 entity에 접근할 수 있다. CoreData 프레임워크는 사용자가 정의한 entity들을 모두 &lt;code&gt;NSManagedObject&lt;/code&gt;라는 클래스의 서브 클래스로 만들어서 관리한다. 위에서 만든 &lt;code&gt;Person&lt;/code&gt;또한 CoreData 프레임워크에 의해 같은 이름을 가진 &lt;code&gt;NSManagedObject&lt;/code&gt; 클래스로 자동으로(자동으로 생성하지 않도록 한 후 프로그래머가 임의로 추가할 수도 있다.) 정의된다. 어쨋든 데이터베이스에 값을 추가하기 위해 해당 entity에 해당하는 &lt;code&gt;NSManagedObject&lt;/code&gt; 인스턴스를 만들어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let description= NSEntityDescription.entity(forEntityName: &amp;quot;Person&amp;quot;, in: context)!
let person = Person(entity: description, into: context)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;NSManagedObject&lt;/code&gt;를 상속받는 &lt;code&gt;Person&lt;/code&gt;이라는 클래스가 자동으로 생성된다고 했기 때문에 &lt;code&gt;Person&lt;/code&gt;클래스를 따로 정의하지 않아도 바로 사용할 수 있다. 정의된 코드를 보면 &lt;code&gt;NSManagedObject&lt;/code&gt;를 상속받은 것을 볼 수 있을 것이다. 이 클래스에는 위에서 추가한 &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;age&lt;/code&gt; 등 attribute들도 자동으로 생성되어 있다. &lt;/p&gt;
&lt;p&gt;임의로 하나의 &lt;code&gt;Person&lt;/code&gt; 데이터를 만들어봤다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let description = NSEntityDescription.entity(forEntityName: &amp;quot;Person&amp;quot;, in: context) {
    let person = Person(entity: description, into: context)
    person.name = &amp;quot;미랑이&amp;quot;
    person.age = 12
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저장은 더 간단하다. &lt;code&gt;NSPersistentContainer&lt;/code&gt;에서 &lt;code&gt;context&lt;/code&gt;를 얻어왔고, 그 안에 &lt;code&gt;Person&lt;/code&gt; 인스턴스를 만들어서 값을 세팅했기 때문에 &lt;code&gt;context&lt;/code&gt;를 저장하기만 하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;do {
    try context.save()
} catch {
    print(error.localizedDescription)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;viewDidLoad&lt;/code&gt;에 코드를 추가했으니 실행만 하면 미랑이라는 person 데이터가 저장될 것이다. 실제로 저장되었는지 확인하기 위해 sqlite 파일을 열어볼 수도 있는데, 이 때 저장되는 파일의 위치를 알아내기 위해서 AppScheme 설정에 들어가 아래 두 argument를 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biKYGp/btrj9mqP8XP/KkHdkITFkBJHZQvBtf0Mt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biKYGp/btrj9mqP8XP/KkHdkITFkBJHZQvBtf0Mt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biKYGp/btrj9mqP8XP/KkHdkITFkBJHZQvBtf0Mt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiKYGp%2Fbtrj9mqP8XP%2FKkHdkITFkBJHZQvBtf0Mt1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;
&lt;li&gt;&lt;code&gt;-com.apple.CoreData.SQLDebug 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-com.apple.CoreData.Logging.stderr 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;앱을 실행하면 콘솔창에 생성된 sqlite 파일의 경로가 출력되는 것을 볼 수 있다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/Library/Developer/CoreSimulator/Devices/{시뮬레이터 아이디}/data/Containers/Data/Application/{앱 아이디}/Library/Application Support/CoreDataTest.sqlite&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 파일을 열어보면(sqlite 파일을 열어볼 수 있는 다양한 에디터들이 있다.) 추가한 미랑이 정보가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEnwhf/btrklXWNaJ5/WiGeviT2xwq4CN1DRHwUq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEnwhf/btrklXWNaJ5/WiGeviT2xwq4CN1DRHwUq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEnwhf/btrklXWNaJ5/WiGeviT2xwq4CN1DRHwUq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEnwhf%2FbtrklXWNaJ5%2FWiGeviT2xwq4CN1DRHwUq1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;보통 데이터베이스에서 정보를 가져올 때 쿼리문을 사용하는데 CoreData 프레임워크는 쿼리문을 사용하지 않고 제공하는 함수를 사용해서 데이터베이스를 조회할 수 있다. 기본적으로 수많은 데이터를 저장하기 때문에 배열로 조회가 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;do {
    let people = try context.fetch(Person.fetchRequest())

    people.forEach { person in
        print(person.name!)  // 미랑이
    }
} catch {
    print(error.localizedDescription)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;근데 entity를 만들 때 분명히 Optional을 체크하지 않았는데 &lt;code&gt;name&lt;/code&gt;이 왜 &lt;code&gt;String?&lt;/code&gt;이 된건지는 잘 모르겠다.&lt;/p&gt;
&lt;p&gt;데이터를 저장하고 불러오는 간단한 예제를 빠르게 만들어봤으니 이제 이 예제를 하나씩 되짚어가면서 CoreData 프레임워크의 Core를 하나씩 알아보자.&lt;/p&gt;</description>
      <category>Programming/IOS</category>
      <category>ios</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/238</guid>
      <comments>https://eastroot1590.tistory.com/entry/Core-Data-1#entry238comment</comments>
      <pubDate>Mon, 8 Nov 2021 23:21:00 +0900</pubDate>
    </item>
    <item>
      <title>iOS Expert | 상단에 고정시킬 수 있는 StickyHeader</title>
      <link>https://eastroot1590.tistory.com/entry/iOS-Expert-%EC%83%81%EB%8B%A8%EC%97%90-%EA%B3%A0%EC%A0%95%EC%8B%9C%ED%82%AC-%EC%88%98-%EC%9E%88%EB%8A%94-StickyHeader</link>
      <description>&lt;p&gt;일반 StickyHeader는 스크롤을 아래로 내렸을 때 위쪽에 윗변이 붙어있는 것 처럼 보이는 화면을 말한다. 하지만 헤더에는 스크롤을 아래로 내렸을 때도 계속 보여야 하는 필수적인 정보가 포함될 수도 있다. StickyHeader 효과를 그대로 가지고 있으면서 특헤더 중 특정 아래 영역을 항상 보이게 하는 autolayout을 만들어보자.&lt;/p&gt;
&lt;p&gt;스토리보드로 만들면 만들 때는 쉽지만 유지보수 하기가 어렵기 때문에 스토리보드를 사용하지 않고 모두 코드로 작성해보도록 하겠다. 스토리보드를 사용하지 않고 코드로 화면을 만드는 방법은 아래 글을 참고하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://eastroot1590.tistory.com/entry/IOS-Beginner-storyboard-탈출?category=848449&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스토리보드 탈출&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 scrollView를 추가한다. autolayout이 중요하기 때문에 constraint 설정 부분을 자세히 봐야 한다. 참고로 모든 view를 추가하는 코드는 viewDidLoad()에 작성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618233753288&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let scrollView = UIScrollView()
scrollView.backgroundColor = .systemBlue
// 1
scrollView.delegate = self
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
// 2
NSLayoutConstraint.activate([
	scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
	scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
	scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
	scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;스크롤을 할 떄마다 헤더의 layout을 바꿔줘야 하기 때문에 delegate를 설정한다.&lt;/li&gt;
&lt;li&gt;화면에 꽉 차도록 layout을 설정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618233789658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let contentView = UIView()
contentView.backgroundColor = .systemGreen
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
// 1
NSLayoutConstraint.activate([
	contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
	contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
	contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;이렇게 까지만 하면 layout 에러가 발생하지만 밑에서 추가할 다른 view들에 맞게 contentView의 bottomAnchor를 설정할 것이기 때문에 일단 부분적으로 적용시켰다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618233816113&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// title
let titleLabel = UILabel()
titleLabel.text = &quot;제목&quot;
titleLabel.font = .boldSystemFont(ofSize: 24)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(titleLabel)
NSLayoutConstraint.activate([
	titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
	titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12)
])

// dummy cell
let cell = UIView()
cell.backgroundColor = .systemPink
cell.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(cell)
NSLayoutConstraint.activate([
	cell.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12),
	cell.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
	cell.widthAnchor.constraint(equalTo: contentView.widthAnchor, constant: -24),
	cell.heightAnchor.constraint(equalToConstant: 1000),
	// 1
	cell.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12)
])

contentView.layoutIfNeeded()
scrollView.contentSize = CGSize(width: view.frame.width, height: contentView.frame.height)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;스크롤을 할 수 있게 충분히 큰 view를 추가하고 bottom을 설정해서 자동으로 스크롤 범위가 정해질 수 있도록 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618233834807&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
scrollView.contentInset.top = headerHeightMax

let header = UIView()
header.backgroundColor = .systemPurple
header.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(header)
NSLayoutConstraint.activate([
	// 2
	header.bottomAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.topAnchor, constant: headerHeightMin),
	header.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
	header.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
// 3
headerHeightAnchor = header.heightAnchor.constraint(greaterThanOrEqualToConstant: headerHeightMax)
headerHeightAnchor?.isActive = true
headerTopAnchor = header.topAnchor.constraint(greaterThanOrEqualTo: scrollView.topAnchor, constant: -headerHeightMax)
headerTopAnchor?.isActive = true&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;header 크기 만큼 inset을 준다.&lt;/li&gt;
&lt;li&gt;아래쪽 부분이 언제나 화면에 보이게 하기 위해서 bottomAnchor를 설정한다.&lt;/li&gt;
&lt;li&gt;스크롤 할 때 마다 sticky 효과를 만드려면 height와 top anchor는 delegate함수에서 추가적인 layout이 필요하다. delegate에서 접근하기 쉽게 따로 변수로 빼서 설정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618233850608&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension MainViewController: UIScrollViewDelegate {
	func scrollViewDidScroll(_ scrollView: UIScrollView) {
		// 1
		if scrollView.contentOffset.y &amp;lt; -scrollView.adjustedContentInset.top {
			headerHeightAnchor?.constant = -scrollView.contentOffset.y
			headerTopAnchor?.constant = scrollView.contentOffset.y
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;스크롤을 내릴 때만 layout을 다시 계산해서 sticky 효과를 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2021-04-12 오후 10.19.46.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1468&quot; width=&quot;300&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgIWY6/btq2lqDR7aY/UXj9WOWZUiiOjQWKQD2k81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgIWY6/btq2lqDR7aY/UXj9WOWZUiiOjQWKQD2k81/img.png&quot; data-alt=&quot;기본 상태. 각 뷰를 구분하기 위해 색을 다르게 설정했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgIWY6/btq2lqDR7aY/UXj9WOWZUiiOjQWKQD2k81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgIWY6%2Fbtq2lqDR7aY%2FUXj9WOWZUiiOjQWKQD2k81%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-04-12 오후 10.19.46.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1468&quot; width=&quot;300&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;figcaption&gt;기본 상태. 각 뷰를 구분하기 위해 색을 다르게 설정했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2021-04-12 오후 10.20.02.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1468&quot; width=&quot;300&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMaUEG/btq2niFNCxQ/nUxheoJJPSdvnwsLEZWBFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMaUEG/btq2niFNCxQ/nUxheoJJPSdvnwsLEZWBFK/img.png&quot; data-alt=&quot;스크롤을 올렸을 때 보라색 헤더가 상단에 고정된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMaUEG/btq2niFNCxQ/nUxheoJJPSdvnwsLEZWBFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMaUEG%2Fbtq2niFNCxQ%2FnUxheoJJPSdvnwsLEZWBFK%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-04-12 오후 10.20.02.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1468&quot; width=&quot;300&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;figcaption&gt;스크롤을 올렸을 때 보라색 헤더가 상단에 고정된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Programming/IOS</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/237</guid>
      <comments>https://eastroot1590.tistory.com/entry/iOS-Expert-%EC%83%81%EB%8B%A8%EC%97%90-%EA%B3%A0%EC%A0%95%EC%8B%9C%ED%82%AC-%EC%88%98-%EC%9E%88%EB%8A%94-StickyHeader#entry237comment</comments>
      <pubDate>Mon, 12 Apr 2021 22:28:51 +0900</pubDate>
    </item>
    <item>
      <title>Design Pattern - 옵저버 패턴 Observer</title>
      <link>https://eastroot1590.tistory.com/entry/Design-Pattern-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-Observer</link>
      <description>&lt;h2&gt;목적&lt;/h2&gt;
&lt;p&gt;특정 인스턴스의 상태를 관찰(observe)하고 있는 구독자에게 변화를 발행(publish)한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;observer.png&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;1238&quot; width=&quot;577&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NBSxg/btqZDQdhaDW/LoH5Gb4OWDciHFM4weaOGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NBSxg/btqZDQdhaDW/LoH5Gb4OWDciHFM4weaOGk/img.png&quot; data-alt=&quot;관심있는 무언가를 관찰(observe)한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NBSxg/btqZDQdhaDW/LoH5Gb4OWDciHFM4weaOGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNBSxg%2FbtqZDQdhaDW%2FLoH5Gb4OWDciHFM4weaOGk%2Fimg.png&quot; data-filename=&quot;observer.png&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;1238&quot; width=&quot;577&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;figcaption&gt;관심있는 무언가를 관찰(observe)한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;방법&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Observer.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;266&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csiLBp/btqZDOfuHNy/sYtiUQiOnNZUfqodK7ucF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csiLBp/btqZDOfuHNy/sYtiUQiOnNZUfqodK7ucF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csiLBp/btqZDOfuHNy/sYtiUQiOnNZUfqodK7ucF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsiLBp%2FbtqZDOfuHNy%2FsYtiUQiOnNZUfqodK7ucF0%2Fimg.png&quot; data-filename=&quot;Observer.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;266&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;ol&gt;
&lt;li&gt;상태 변화를 발행할 수 있는 Publisher 인터페이스를 만든다. (GoF 옵저버 패턴에서는 Subject)
&lt;ul&gt;
&lt;li&gt;subscribe : Observer를 추가한다.&lt;/li&gt;
&lt;li&gt;unsubscribe : Observer를 제거한다.&lt;/li&gt;
&lt;li&gt;publish : Observer에 변화된 상태를 발행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Publisher를 관측할 수 있는 Observer 인터페이스를 만든다. 구현에 따라 두가지 방법이 있다.
&lt;ul&gt;
&lt;li&gt;Publisher로부터 변화된 상태를 갱신받는다(push). 정해진 정보만 갱신 받을 수 있다는 단점이 있다.&lt;/li&gt;
&lt;li&gt;update()에 publisher를 자신을 전달해서 필요한 데이터를 얻을 수 있도록 한다(pull). 원하는 정보를 받을 수 있지만 subject의 getter를 호출하기 때문에 관측하고 갱신한다는 패턴 목적에 조금 어긋난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;예제&lt;/h2&gt;
&lt;p&gt;String value를 구독하고 발행할 수 있도록 push 방식의 옵저버 패턴을 구현해보자. 먼저 Publisher(Subject), Observer 인터페이스를 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1615209121389&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol StringPublisher {
    var observers: [StringObserver] { get }
    var value: String { get }
    
    func subscribe(_ observer: StringObserver)
    
    func publish()
}

protocol StringObserver: class {
    func update(_ item: String)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;StringPublisher를 채택하여 이름 클래스를 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1615209137413&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
class PersonName: StringPublisher {
    var observers: [StringObserver] = []
    var value: String
    
		// 2
    var name: String {
        get {
            return self.value
        }
        set {
            self.value = newValue
            publish()
        }
    }
    
    init(_ initialValue: String) {
        self.value = initialValue
    }
    
    func subscribe(_ observer: StringObserver) {
        observers.append(observer)
    }
    
    func unsubscribe(_ observer: StringObserver) {
        observers.removeAll { $0 === observer }
    }
    
		// 3
    func publish() {
        for observer in observers {
            observer.update(value)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;StringPublisher protocol을 채택한다.&lt;/li&gt;
&lt;li&gt;value에 대한 getter, setter 역할을 한다. 새로운 값으로 set 되었을 때 publish()를 호출하여 변경사항을 발행한다.&lt;/li&gt;
&lt;li&gt;자기 자신을 구독하고 있는 observer들의 update()를 호출하여 값을 발행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사람의 이름은 학교에서도 사용하고, 회사에서도 사용할 수 있다. 학교와 회사는 이름을 관찰하고 있다가 이름이 바뀌면 적절히 업데이트 할 수 있어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1615209152889&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class School: StringObserver {
    func update(_ item: String) {
        print(&quot;school updated \(item)&quot;)
    }
}

class Company: StringObserver {
    func update(_ item: String) {
        print(&quot;company updated \(item)&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 만든 publisher와 observer를 사용하면서 옵저버 패턴이 어떻게 사용되는지 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1615209167103&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let kim = PersonName(&quot;KIM&quot;)

let school = School()
let company = Company()

// 1
kim.subscribe(school)
kim.subscribe(company)

// 2
kim.name = &quot;PARK&quot;
// school updated PARK
// company updated PARK

// 3
kim.unsubscribe(school)

// 4
kim.name = &quot;CHOI&quot;
// company updated CHOI&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;김씨는 학교와 회사에 소속되어 있다.&lt;/li&gt;
&lt;li&gt;어느날 김씨는 자신의 이름을 PARK으로 개명했다. 개명된 이름을 김씨가 소속되어 있던 학교와 회사 모두 개명된 이름을 전달 받았다.&lt;/li&gt;
&lt;li&gt;김씨가 학교를 졸업하여 더이상 학교 소속이 아니게 되었다.&lt;/li&gt;
&lt;li&gt;김씨가 자신의 이름을 CHOI로 개명하자 이번에는 회사만 전달 받았다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;push 방식으로 구현한 위 예제의 경우 이름을 표현하는 한 변수에 대한 구독을 하고 있지만, PersonName에 다른 값을 구독하고 싶다면 pull 방식을 사용할 수 있다. 구현에 따라 달라질 수 있지만 update()에 publisher 자기 자신을 전달하여 observer가 직접 원하는 값을 얻어가도록 구현할 수 있다.&lt;/p&gt;</description>
      <category>Programming/Design</category>
      <category>DesignPattern</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/236</guid>
      <comments>https://eastroot1590.tistory.com/entry/Design-Pattern-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-Observer#entry236comment</comments>
      <pubDate>Mon, 8 Mar 2021 22:13:23 +0900</pubDate>
    </item>
    <item>
      <title>Swift Beginner - struct와 class의 차이</title>
      <link>https://eastroot1590.tistory.com/entry/Swift-Beginner-struct%EC%99%80-class%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
      <description>&lt;p&gt;Swift에는 4가지 기본 인터페이스가 있다. 가장 추상적인 인터페이스인 protocol부터 OOP에서 가장 많이 사용하는 class, 그리고 struct, enum 등이 있다. C++에서 struct와 enum은 기본 자료형을 묶어서 구조체로 만들거나 숫자 리터럴을 키워드로 변경하기 위한 용도 등으로 사용했지만 Swift는 좀 더 많은 기능을 가지고 있다. 예를 들어 struct와 enum 모두 class처럼 함수를 가질 수 있다.&lt;/p&gt;
&lt;p&gt;이로써 struct와 class의 경계가 모호해졌다고 볼 수 있지만 여전히 두 인터페이스는 다른 사용성을 가지고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;차이점&lt;/h2&gt;
&lt;h3&gt;value type vs reference type&lt;/h3&gt;
&lt;p&gt;struct는 &lt;b&gt;value type&lt;/b&gt;이고 class는 &lt;b&gt;reference type&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;때문에 struct는 함수의 파라미터로 전달될 때 값이 통째로 복사되어 전달되고, class는 메모리 주소가 복사되어 함수 내에서도 동일한 메모리에 접근할 수 있다. 즉, struct를 전달받은 함수에서 아무리 값을 바꿔도 외부에 있는 struct 인스턴스는 전혀 영향을 받지 않지만 class를 전달받은 함수에서 class의 내용을 변경하면, 외부에서도 그 변경이 적용된다.&lt;/p&gt;
&lt;h3&gt;상속 받을 수 있는 인터페이스의 제한&lt;/h3&gt;
&lt;p&gt;struct는 protocol만 상속 받을 수 있고, class는 protocol 또는 다른 class를 상속 받을 수 있다.&lt;/p&gt;
&lt;p&gt;이유는 type의 차이를 생각해 보면 유추할 수 있는데, value type은 함수와 변수의 선언만 할 수 있는 protocol만 상속 받아서 언제나 같은 크기의 데이터를 정의할 수 있고, reference type은 protocol 뿐 아니라 다른 class를 상속 받아서 데이터의 크기가 커져도 변경된 크기에 따라 동적으로 메모리를 할당할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;궁금한 점&lt;/h2&gt;
&lt;h3&gt;struct가 class의 인스턴스를 가지고 있으면 어떻게 될까&lt;/h3&gt;
&lt;p&gt;struct는 class를 상속받을 수는 없지만 class의 인스턴스를 멤버로 가지고 있을 수 있다. class 인스턴스를 가지고 있는 struct를 복사하면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614945781652&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Cls {
	var index: Int = 0

	// 1
	init() {
		print(&quot;init&quot;)
	}

	deinit {
		print(&quot;deinit&quot;)
	}

	func increase() {
		index += 1
	}
}

struct Stt {
	var number: Int
	// 2
	let cls: Cls

	func show() {
		print(&quot;\(number) \(cls.index)&quot;)
	}
}

// 3
var s1 = Stt(number: 5, cls: Cls())  // init
s1.show()  // 5 0
s1.cls.increase()
s1.show()  // 5 1&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;class Cls의 생성자와 소멸자가 실행될 때 알 수 있도록 print()를 추가한다.&lt;/li&gt;
&lt;li&gt;struct Stt가 class Cls의 인스턴스를 가지고 있다.&lt;/li&gt;
&lt;li&gt;struct Stt의 인스턴스를 생성하고 Cls.index를 증가시켰다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기에서 s1을 복사하면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614945814000&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
var s2 = s1  // copy
s2.show()  // 5 1

// 2
s2.cls.increase()
s1.show()  // 5 2&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;s2에 s1 struct를 복사한 후 출력해보면 그대로 5 1이 출력된다.&lt;/li&gt;
&lt;li&gt;s2.cls.index를 증가시키고 s1을 출력해도 변화가 적용된 것을 볼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그렇다면 이 경우 Cls의 메모리는 언제 해제될까? 메모리 해제 시점을 알아보기 위해 미리 deinit()에 print()를 추가해 두었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614945829939&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
var s1 = Stt(cls: Cls())
var s2 = Stt(cls: Cls())  // deinit&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;s1, s2 모두 새로운 Stt를 대입하면 s2에 대입했을 때 deinit이 출력된다. 즉, s1의 값이 s2로 복사되면서 cls에 대한 ARC가 증가했다는 뜻이다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Programming/Swift</category>
      <category>swift</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/235</guid>
      <comments>https://eastroot1590.tistory.com/entry/Swift-Beginner-struct%EC%99%80-class%EC%9D%98-%EC%B0%A8%EC%9D%B4#entry235comment</comments>
      <pubDate>Fri, 5 Mar 2021 21:04:14 +0900</pubDate>
    </item>
    <item>
      <title>RxSwift Beginner | Objservables &amp;amp; Subjects in Practice(번역)</title>
      <link>https://eastroot1590.tistory.com/entry/RxSwift-Beginner-Objservables-Subjects-in-Practice%EB%B2%88%EC%97%AD</link>
      <description>&lt;p&gt;사실 이전 번역은 불필요한 농담이나 비유는 번역을 안하고 넘어갔지만 이번에는 웬만하면 다 번역을 해보려고 노력했다. 한글로 된 책을 읽을 때는 미쳐 몰랐는데 개발자들의 이 말장난들을 어쩜 그렇게 재치있게 번역했는지, 전공 서적을 번역하는 분들 정말 존경스럽다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.raywenderlich.com/books/rxswift-reactive-programming-with-swift/v4.0/chapters/4-observables-subjects-in-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;원본 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 observable과 subject가 어떻게 다르게 작동하며, playground에서 어떻게 만들어서 실험하는지 알게 되었다.&lt;/p&gt;
&lt;p&gt;하지만 매일매일 observable을 사용해서 UI를 데이터 모델과 연결하거나 데이터 모델로부터 새로운 controller를 제공하는 코드를 작성하는 것은 어려울 수 있다.&lt;/p&gt;
&lt;p&gt;새롭게 배운 기술을 실제 세계에 적용시키는 것은 쉽지 않다. 챕터2 observable, 챕터3 subject에서 이론적인 부분을 다루었으니 이번 챕터는 실제로 적용되는 코드를 작성하면서 체화시켜보자.&lt;/p&gt;
&lt;p&gt;&quot;...in practice&quot;에서는 실제로 애플리케이션을 완성시켜 볼 것이다. 시작 프로젝트는 Rx 코드를 포함하지 않는다. 추후에 추가되는 기능을 RxSwift framework로 작성할 것이며, 필요한 기능이 있을 때 마다 새로운 reactive 기술에 대해 배우게 될 것이다.&lt;/p&gt;
&lt;p&gt;그렇다고 해서 새로 배우는 내용이 적다는 뜻은 아니다.&lt;/p&gt;
&lt;p&gt;이 챕터에서는 사용자가 사진 모음을 만들 수 있도록 하는 애플리케이션을 observable을 사용해서 reactive 하게 만들어볼 것이다.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;시작 프로젝트 &lt;b&gt;Combinestagram&lt;/b&gt;을 연다. 발음하기 힘들어서 마켓이 올리기 좋은 이름은 아닌 것같다. 하지만 곧 익숙해질 것이다.&lt;/p&gt;
&lt;p&gt;필요한 pods를 모두 설치하고 &lt;b&gt;Combinestagram.xcworkspace&lt;/b&gt;를 연다. 자세한 방법이 기억나지 않는다면 챕터1 Hello RxSwift를 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Asset/Main.storyboard&lt;/b&gt;를 선택하면 인터페이스를 만들 수 있는 GUI를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;storyboard.png&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;624&quot; width=&quot;758&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3Y0h/btqU3pxOgaF/sPD5BcDp4OtLjAVdc5T0V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3Y0h/btqU3pxOgaF/sPD5BcDp4OtLjAVdc5T0V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3Y0h/btqU3pxOgaF/sPD5BcDp4OtLjAVdc5T0V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3Y0h%2FbtqU3pxOgaF%2FsPD5BcDp4OtLjAVdc5T0V1%2Fimg.png&quot; data-filename=&quot;storyboard.png&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;624&quot; width=&quot;758&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;첫 화면에서 사용자는 현재 사진 모음과 현재 목록을 지울 수 있는 clear 버튼, 현재 목록을 저장할 수 있는 finished 버튼을 볼 수 있다. 그리고 우측 상단에 있는 +버튼을 누르면 카메라 롤에 있는 사진을 볼 수 있는 새로운 view controller를 볼 수 있다. 사용자는 사진 썸네일을 탭 해서 사진 모음에 사진을 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;view controller와 storyboard는 이미 연결되어 있고, &lt;b&gt;UIImage+Collage.swift&lt;/b&gt;를 열어서 실제로 어떻게 추가되었는지 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이 챕터에서는 새로운 기술을 추가하는 방법에 집중할 것이다. 이제 시작할 시간이다!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Using a subject/relay in a view controller&lt;/h2&gt;
&lt;p&gt;controller에 선택된 사진을 값으로 가지는 BehaviorRelay&amp;lt;[UIImage]&amp;gt;를 추가하는 것 부터 시작해보자. 챕터3에서 배웠듯이 BehaviorRelay는 기존 변수와 비슷하게 사용할 수 있다.(언제든지 저장하고 있는 값을 변경할 수 있다.) 이 간단한 코드부터 시작해서 점차 subject와 custom observable로 발전해 나갈 것이다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;MainViewController.swift&lt;/b&gt;를 열고 다음 코드를 body에 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611822515897&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private let bag = DisposeBag()
private let images = BehaviorRelay&amp;lt;[UIImage]&amp;gt;(value: [])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 두 프로퍼티는 외부에서 사용할 일이 없기 때문에 private로 선언한다. 와! 캡슐화!&lt;/p&gt;
&lt;p&gt;dispose bag의 소유자는 view controller이다. 따라서 view controller가 해제될 때 모든 observable의 구독이 해제(dispose)된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;dispose bag.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;692&quot; width=&quot;643&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzBzf/btqUZvkWaw4/TZXZkH69aXZOe0YF94yyOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzBzf/btqUZvkWaw4/TZXZkH69aXZOe0YF94yyOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzBzf/btqUZvkWaw4/TZXZkH69aXZOe0YF94yyOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzBzf%2FbtqUZvkWaw4%2FTZXZkH69aXZOe0YF94yyOk%2Fimg.png&quot; data-filename=&quot;dispose bag.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;692&quot; width=&quot;643&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;이건 Rx 구독의 메모리 관리를 편리하게 해 준다. 단지 구독을 dispose bag에 등록하는 것으로 view controller와 함께 메모리에서 해제되는 구독을 만들 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 안타깝게도 위 코드는 그렇게 동작하지 않는다. MainViewController는 root view controller이기 때문에 애플리케이션이 종료할 때 까지 살아있기 때문이다. view controller와 함께 해제되는 dispose bag의 실제 동작은 이 챕터 뒷부분에서 만들 다른 view controller에서 볼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;처음에는 항상 같은 사진으로 사진모음을 만든다. 걱정하지 마라 Asset에 포함된 사진들은 바르셀로나 교외에서 찍은 멋진 사진들이다. 일단은 +를 누를 때 마다 항상 같은 사진이 추가되도록 만들 것이다.&lt;/p&gt;
&lt;p&gt;actionAdd()를 찾아서 다음 코드를 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611822572620&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let newImages = images.value + [UIImage(named: &quot;IMG_1907.jpg&quot;)!]
images.accept(newImages)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;첫째로, images가 방출하는 가장 최근 값에 새로운 UIImage를 추가한다. UIImage를 강제로 unwrap하는 것은 일단 무시하자. 이 챕터에서는 가능한 한 쉽게 중요 기능에만 집중할 수 있도록 하자.&lt;/p&gt;
&lt;p&gt;그 다음, relay의 accept(_:)를 사용해서 images를 구독하고 있는 다른 구독자에게 최근 값을 방출할 수 있도록 했다.&lt;/p&gt;
&lt;p&gt;images relay의 초기값은 비어있는 배열이었고, 사용자가 +버튼을 누를 때 마다 relay는 next 이벤트로 새로운 이미지가 추가된 배열을 방출한다.&lt;/p&gt;
&lt;p&gt;사용자의 선택을 초기화 하기 위해서 actionClear()로 이동해서 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611822584811&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;images.accept([])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;짧은 코드로 사용자의 입력을 깔끔하게 처리했다. 이제 images를 관찰(observe)하면서 결과를 화면에 표시할 준비가 끝났다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Adding photos to the callage&lt;/h2&gt;
&lt;p&gt;이제 연결된 images가 있으니, 사진 모음 프리뷰를 업데이트 할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;viewDidLoad()에 images의 구독을 추가하자. images relay이지만, ObservableType이기 때문에 observable처럼 직접 구독할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611822597851&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;images
	.subscribe(onNext: { [weak imagePreview] photos in
		guard let preview = imagePreview else { return }
		preview.image = photos.collage(size: preview.frame.size)
	})
	.disposed(by: bag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;images의 next 이벤트를 구독했다. 매 이벤트 마다 collage(images:size:)라는 helper로 collage를 생성한다. 마지막으로 view controller의 dispose bag에 등록한다.&lt;/p&gt;
&lt;p&gt;이 챕터에서는 viewDidLoad()에서 observable을 구독한다. 이 책의 후반부에는 점차 다른 클래스에서도 구독하는 예제를 살펴볼 것이고 마지막에는 MVVM 구조로 만들어볼 것이다. 이제 사용자의 입력에 따라 images를 업데이트 하고 UI도 변경했다.&lt;/p&gt;
&lt;p&gt;실제로 실행해서 잘 동작하는지 보자. +버튼을 네번 눌러서 사진을 4개 추가하면 이런 모습이 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;4photo result.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;472&quot; width=&quot;599&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbYMgo/btqU52u0OoW/5XL9xWVVpj2HMojzDsNVg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbYMgo/btqU52u0OoW/5XL9xWVVpj2HMojzDsNVg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbYMgo/btqU52u0OoW/5XL9xWVVpj2HMojzDsNVg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbYMgo%2FbtqU52u0OoW%2F5XL9xWVVpj2HMojzDsNVg0%2Fimg.png&quot; data-filename=&quot;4photo result.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;472&quot; width=&quot;599&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;정말 쉽군!&lt;/p&gt;
&lt;p&gt;완성된 모습이 별볼일 없어 보일 수 있다. 하지만 조만간 실제 카메라롤에서 사진을 선택할 수 있는 기능을 추가할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Driving a complex view controller UI&lt;/h2&gt;
&lt;p&gt;지금까지 결과를 실행해 보면 UI를 좀 더 스마트하게 개선할 수 있는 점들을 알아챌 수 있을 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 선택된 사진이 하나도 없다면 clear 버튼을 비활성화 시킬 수 있다. (유저가 clear를 시킨 직후에도)&lt;/li&gt;
&lt;li&gt;마찬가지로 save 버튼 또한 항상 활성화 되어있을 필요는 없다.&lt;/li&gt;
&lt;li&gt;현재 선택된 사진의 개수가 홀수라면 save버튼을 비활성화 시킬 수도 있다. (사진이 홀수라면 완성된 사진 모음에 빈칸이 생길 것이다.)&lt;/li&gt;
&lt;li&gt;너무 많은 사진으로 만든 모음은 결과가 이상하게 나오기 때문에 선택할 수 있는 최대 사진을 6장으로 제한할 수도 있다.&lt;/li&gt;
&lt;li&gt;마지막으로 현재 선택한 사진의 제목을 화면에 표시해줄 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 목록을 다시한번 천천히 읽어보면 reactive가 아닌 방법으로 위 내용을 구현하는게 여간 어려운게 아니라는 것을 느낄 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;하지만 RxSwift에서는 간단한 코드로 images를 구독하고 최신 상태를 업데이트 받을 수 있다.&lt;/p&gt;
&lt;p&gt;viewDidLoad()에 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611822630661&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;images
	.subscribe(onNext: { [weak self] photos in
		self?.updateUI(photos: photos)
	})
	.disposed(by: bag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;선택된 사진이 변경될 때 마다 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;updateUI(photos:)&lt;/span&gt;를 호출하도록 했다. updateUI라는 함수가 없기 때문에 클래스의 아무곳에 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611822640576&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func updateUI(photos: [UIImage]) {
	buttonSave.isEnabled = photos.count &amp;gt; 0 &amp;amp;&amp;amp; photos.count % 2 == 0
	buttonClear.isEnabled = photos.count &amp;gt; 0
	itemAdd.isEnabled = photos.count &amp;lt; 6
	title = photos.count &amp;gt; 0 ? &quot;\(photos.count) photos&quot; : &quot;Collage&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 생각했던 개선에 맞게 UI를 업데이트하는 코드를 추가했다. 모든 코드는 한 자리에 있고 가독성도 좋다. 다시 애플리케이션을 실행하고 모든것이 제대로 동작하는지 테스트 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;rule test.png&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;438&quot; width=&quot;619&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1qhKE/btqUYHlFdDL/BSAPOkqMj5CwKV7cZNEs40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1qhKE/btqUYHlFdDL/BSAPOkqMj5CwKV7cZNEs40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1qhKE/btqUYHlFdDL/BSAPOkqMj5CwKV7cZNEs40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1qhKE%2FbtqUYHlFdDL%2FBSAPOkqMj5CwKV7cZNEs40%2Fimg.png&quot; data-filename=&quot;rule test.png&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;438&quot; width=&quot;619&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;이제 Rx를 iOS에 적용했을 때 얼마나 이점이 많은지 보일 것이다. 지금까지 작성한 코드를 보자. 얼마나 짧고 간결한가!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Talking to other view controllers via subjects&lt;/h2&gt;
&lt;p&gt;이번 섹션에서는 사용자가 카메라롤에서 임의로 선택한 사진을 main view controller와 연동할 수 있도록 하는 PhotosViewController를 만들어볼 것이다. 결과물은 아마 이전보다 더 흥미로울 것이다.&lt;/p&gt;
&lt;p&gt;첫째로, navigation stack에 PhotosViewController를 추가해야 한다. &lt;b&gt;MainViewController.swift&lt;/b&gt;를 열고 actionAdd()함수를 찾아서 다음 코드의 주석을 해제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611822680047&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let photosViewController = storyboard!.instantiateViewController(withIdentifier: &quot;PhotosViewController&quot;) as! PhotosViewController

navigationController!.pushViewController(photosViewController, animated: true)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;storyboard에서 만든 PhotosViewController의 인스턴스를 생성하고 navigation stack에 추가했다. 실행해서 +버튼을 눌러 카메라롤이 잘 나오는지 보자.&lt;/p&gt;
&lt;p&gt;최초로 실행했을 때는 카메라롤에 대한 접근을 허용하도록 하는 경고 팝업을 보게 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;library warning.png&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;470&quot; width=&quot;503&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvQnAY/btqU3pxOHbi/519knctCd6EMixp9MGxRx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvQnAY/btqU3pxOHbi/519knctCd6EMixp9MGxRx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvQnAY/btqU3pxOHbi/519knctCd6EMixp9MGxRx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvQnAY%2FbtqU3pxOHbi%2F519knctCd6EMixp9MGxRx0%2Fimg.png&quot; data-filename=&quot;library warning.png&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;470&quot; width=&quot;503&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;허용(또는 &lt;b&gt;OK&lt;/b&gt;)를 선택하면 photos controller가 어떻게 생겼는지 볼 수 있다. 실제로 실행화면은 실행하고 있는 환경에 따라 다를 수 있고, 앱을 종료하고 다시 실행해야 할 수도 있다.&lt;/p&gt;
&lt;p&gt;이번에는 iPhone 시뮬레이터에 있는 샘플 사진들을 볼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;simulator sample.png&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;486&quot; width=&quot;599&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FRKKY/btqU4CjhnuD/cnbBg3iIWh6oNvEb4GQFHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FRKKY/btqU4CjhnuD/cnbBg3iIWh6oNvEb4GQFHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FRKKY/btqU4CjhnuD/cnbBg3iIWh6oNvEb4GQFHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFRKKY%2FbtqU4CjhnuD%2FcnbBg3iIWh6oNvEb4GQFHK%2Fimg.png&quot; data-filename=&quot;simulator sample.png&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;486&quot; width=&quot;599&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;기존 cocoa 패턴으로 개발한다면 다음 작업은 photos view controller가 main view controller에게 이벤트를 전달할 수 있도록 delegate를 만드는 것이다.(이건 reactive 방식이 아니다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;old cocoa pattern.png&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;712&quot; width=&quot;645&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wYQ95/btqU3oZTjAJ/bn1Ie47Vln3yx7tNTYzHv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wYQ95/btqU3oZTjAJ/bn1Ie47Vln3yx7tNTYzHv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wYQ95/btqU3oZTjAJ/bn1Ie47Vln3yx7tNTYzHv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwYQ95%2FbtqU3oZTjAJ%2Fbn1Ie47Vln3yx7tNTYzHv1%2Fimg.png&quot; data-filename=&quot;old cocoa pattern.png&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;712&quot; width=&quot;645&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;하지만 RxSwift에는 어떤 두 클래스라도 연결할 수 방법이 있다. Observable! observable은 어떠한 메세지도 전달할 수 있기 때문에 따로 protocol을 정의할 필요가 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Creating an observable out of the selected photos&lt;/h2&gt;
&lt;p&gt;이제 PhotosViewController에, 사용자가 카메라롤에서 사진을 선택할 때 마다 next 이벤트를 방출하는 subject를 추가할 것이다. &lt;b&gt;PhotosViewController.swift&lt;/b&gt;를 열고 맨 위에 다음 코드를 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611823020055&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import RxSwift&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PublishSubject는 선택된 사진을 방출할 수 있어야 하지만, 외부에서 접근해서 onNext(_:)를 호출을 하는 것은 원하지 않을 것이다.&lt;/p&gt;
&lt;p&gt;PhotosViewController에 다음 프로퍼티를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823030595&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private let selectedPhotosSubject = PublishSubject&amp;lt;UIImage&amp;gt;()
var selectedPhotos: Observable&amp;lt;UIImage&amp;gt; {
	return selectedPhotosSubject.asObservable()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;선택된 사진을 방출할 수 있는 PublishSubject와 이를 observable로 접근할 수 있는 selectedPhotos라는 public 접근자를 생성했다.&lt;/p&gt;
&lt;p&gt;이 프로퍼티를 구독하면 sequence를 방해하지 않고 선택된 사진을 관찰할 수 있다.&lt;/p&gt;
&lt;p&gt;PhotosViewController에는 이미 카메라롤에 있는 사진들을 읽고 collection view로 표현하는 코드가 포함되어 있다. 해야할 일은 사용자가 collection view에서 선택한 사진을 방출하는 코드를 작성하는 일 뿐이다.&lt;/p&gt;
&lt;p&gt;스크롤을 내려서 collectionView(_:didSelectItemAt:)으로 이동한다. 안에 있는 코드는 선택된 사진을 약간 하이라이트 시켜서 사용자에게 시각적인 정보를 제공한다.&lt;/p&gt;
&lt;p&gt;이어서, imageManager.requestImage(...)는 completion closure에서 선택된 사진에 대한 정보를 가진 image, info 파라미터를 제공한다. 이 closure에서 next 이벤트를 방출할 것이다.&lt;/p&gt;
&lt;p&gt;closure의 guard 아래에 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823041124&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if let isThumbnail = ifno[PHImageResultIsDegradedKey as NSString] as? Bool, !ISThumbnail {
	self?.selectedPhotosSubject.onNext(image)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;선택된 이미지가 썸네일인지 원본인지 확인하기 위해 info dictionary를 사용했다. imageManager.requestImage(...)는 각 버전에 대해 모두 closure를 실행한다. 원본일 때 onNext(_:)를 호출해서 원본 이미지를 방출하게 한다.&lt;/p&gt;
&lt;p&gt;이게 RxSwift가 view controller에서 다른 view controller로 observable sequence를 방출하는 방법이다. delegate 프로토콜이나 다른 헛짓거리는 필요 없다.&lt;/p&gt;
&lt;p&gt;보너스로 프로토콜을 제거함으로써 controller들 간의 관계가 굉장히 간결해졌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;simple relationship.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;666&quot; width=&quot;680&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dARLH3/btqU40qHBdB/7UvQTquANXpsP90kYaaKZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dARLH3/btqU40qHBdB/7UvQTquANXpsP90kYaaKZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dARLH3/btqU40qHBdB/7UvQTquANXpsP90kYaaKZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdARLH3%2FbtqU40qHBdB%2F7UvQTquANXpsP90kYaaKZ0%2Fimg.png&quot; data-filename=&quot;simple relationship.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;666&quot; width=&quot;680&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Observing the sequence of selected photos&lt;/h2&gt;
&lt;p&gt;다음은 &lt;b&gt;MainViewController.swift&lt;/b&gt;로 돌아와서 선택된 사진 sequence를 구독하는 작업을 마무리 할 차례이다.&lt;/p&gt;
&lt;p&gt;actionAdd()를 찾아서 navigation stack에 PhotosViewController를 추가하는 부분 위에 다음 코드를 작성하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611823083141&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;photosViewController.selectedPhotos
	.subscribe(
		onNext: { [weak self] newImage in

		},
		onDisposed: {
			print(&quot;Completed photo selection&quot;)
		}
	)
	.disposed(by: bag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;controller를 stack에 추가하기 전에 selectedPhotos observable을 구독하도록 했다. 특이한 점은 onNext와 함께 onDisposed 이벤트도 처리하도록 구독했다는 점인데 왜 그런지는 잠시 후에 알게된다.&lt;/p&gt;
&lt;p&gt;onNext closure에 처음에 작성했던 것과 같은 다음 코드를 추가한다. 단, 이번에는 정해진 사진이 아니라 카메라롤에서 선택한 사진이 추가될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823092751&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;guard let images = self?.images else { return }
images.accept(images.value + [newImage])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;애플리케이션을 실행하고 카메라롤에서 몇개의 사진을 선택한 후 되돌아와서 결과를 보자. 멋져!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;cool result.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;686&quot; width=&quot;525&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbzcd8/btqUYHzas5I/ZoIIVT6Bj4KdHgnkSXTLoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbzcd8/btqUYHzas5I/ZoIIVT6Bj4KdHgnkSXTLoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbzcd8/btqUYHzas5I/ZoIIVT6Bj4KdHgnkSXTLoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbzcd8%2FbtqUYHzas5I%2FZoIIVT6Bj4KdHgnkSXTLoK%2Fimg.png&quot; data-filename=&quot;cool result.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;686&quot; width=&quot;525&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Disposing subscriptions - review&lt;/h2&gt;
&lt;p&gt;모든 코드가 정상적으로 동작하는 것 처럼 보인다. 하지만 사진을 몇개 추가하고 메인 화면으로 돌아온 후 콘솔을 보자.&lt;/p&gt;
&lt;p&gt;&quot;Completed photo selection&quot;이라는 문구가 보이는가? 아까 onDisposed에서 출력하도록 print를 작성했지만 출력되지 않는다! 그말은 구독이 취소되지 않았다는 뜻이고 아직 메모리를 잡아먹고 있다는 뜻이다!&lt;/p&gt;
&lt;p&gt;왜지? 구독을 하는 코드를 다시 보자. main view controller의 dispose bag에 구독을 등록했다. 이전 챕터에서 설명했던 대로 구독은 sequence가 error나 completed로 종료되거나 dispose bag이 해제될 때 같이 해제된다고 했다.&lt;/p&gt;
&lt;p&gt;bag을 해제하기 위해 main view controller를 해제하거나 photos sequence를 종료하지 않았기 때문에 구독이 종료되지 않는다.&lt;/p&gt;
&lt;p&gt;controller가 화면에서 사라질때(disappear) completed 이벤트를 방출하도록 할 수 있다. 이 방법은 모든 구독자에게 completed 이벤트를 방출해서 자동으로 해제할 수 있도록 도와준다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;PhotosViewController.swift&lt;/b&gt;를 열고 viewWillDisappear(_:)를 찾아서 onCompleted() 이벤트를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823128303&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;selectedPhotosSubject.onCompleted()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;완벽해! 이제 이 챕터의 마지막 단계만 남았다. 구닥다리 옛 함수들을 겁나 쩌는 reactive 함수로 바꿔보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Creating a custom observable&lt;/h2&gt;
&lt;p&gt;지금까지 BehaviorRelay, PublishSubject, 그리고 Observable을 다뤄보았다. 준비운동으로 커스텀 Observable 클래스를 만들고 기존 API 함수들을 reactive로 변환해보자. 사진 모음을 저장하기 위해서 Photos framework를 사용할 것이다.&lt;/p&gt;
&lt;p&gt;PHPhotoLibrary를 확장해서 reactive 기능을 추가할 수도 있지만 쉽게 가자. 이 챕터에서는 PhotoWriter라는 새로운 클래스를 만들 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;photo writer.png&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;308&quot; width=&quot;662&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/laMn1/btqUZulZDjr/fqPGhdfjlKSMt1mmwKYhoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/laMn1/btqUZulZDjr/fqPGhdfjlKSMt1mmwKYhoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/laMn1/btqUZulZDjr/fqPGhdfjlKSMt1mmwKYhoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlaMn1%2FbtqUZulZDjr%2FfqPGhdfjlKSMt1mmwKYhoK%2Fimg.png&quot; data-filename=&quot;photo writer.png&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;308&quot; width=&quot;662&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;사진을 저장하기 위한 observable을 만드는 것은 쉬울 것이다. 사진이 성공적으로 저장되었다면 asset ID와 함께 completed 이벤트를 방출할 수있고, 실패하면 error 이벤트를 방출할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Wrapping an exiting API&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;Classes/PhotoWriter.swift&lt;/b&gt;를 열고(이 파일에는 빨리 시작하기 위해 몇가지 코드가 작성되어 있다.&lt;/p&gt;
&lt;p&gt;첫째로, import를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823155073&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import RxSwift&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 사진을 저장할 observable을 생성할 수 있는 static 함수를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823165625&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static func save(_ image: UIImage) -&amp;gt; Observable&amp;lt;String&amp;gt; {
	return Observable.create { observer in
	
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;save(_:)는 Observable&amp;lt;String&amp;gt;을 생성해서 반환한다. 사진을 저장하고 나서는 각 사진에 대한 고유 정보를 방출할 것이기 때문이다.&lt;/p&gt;
&lt;p&gt;Observable.create()는 새로운 observable을 생성하고, 이제 이 closure에 나머지 내용을 추가해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823175660&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var savedAssetId: String?
PHPhotoLibrary.shared().performChanges({

}, completionHandler: { success, error in

})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;performChanges(_:completionHandler)의 첫번째 closure에서 photo asset을 생성하고 두번째 closure에서 그 asset의 ID 또는 error 이벤트를 방출할 것이다.&lt;/p&gt;
&lt;p&gt;첫번째 closure에 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823187865&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let request = PHAssetChangeRequest.creationRequestForAsset(for: image)
savedAssetId = request.placeholderForCreatedAsset?.localIdentifier&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;PHAssetChangeRequest.creationRequestForAsset(from:)&lt;/span&gt;으로 새로운 photo asset을 만들었고, 그 ID를 &lt;span data-token-index=&quot;2&quot; data-reactroot=&quot;&quot;&gt;savedAssetId&lt;/span&gt;에 저장했다. 두번째 closure에 다음을 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611823198726&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DispatchQueue.main.async {
	if success, let id = savedAssetId {
		observer.onNext(id)
		observer.onCompleted()
	} else {
		observer.onError(error ?? Errors.couldNotSavePhoto)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;성공적으로 savedAssetId에 유효한 ID를 응답 받으면 next 이벤트를 방출하고 completed 이벤트로 종료한다. 응답이 잘못 되었다면 error을 방출한다.&lt;/p&gt;
&lt;p&gt;이것으로 observable sequence 로직이 완성되었다.&lt;/p&gt;
&lt;p&gt;Xcode는 아마 create가 disposable을 반환하지 않았다는 경고를 줄 것이다. 따라서 create의 마지막줄에 Disposable을 반환하는 다음 줄을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823211137&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return Disposables.create()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;완성된 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;save()&lt;/span&gt; 함수는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823221815&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static func save(_ image: UIImage) -&amp;gt; Observable&amp;lt;String&amp;gt; {
	return Observable.create({ observer in
		var savedAssetId: String?
		PHPhotoLibrary.shared().performChanges({
			let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
			savedAssetId = request.placeholderForCreatedAsset?.localIdentifier
		}, completionHandler: { success, error in
			DispatchQueue.main.async {
				if success, let id = savedAssetId {
					observer.onNext(id)
					observer.onCompleted()
				} else {
					observer.onError(error ?? Errors.couldNotSavePhoto)
				}
			}
		})
		return Disposables.create()
	})
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;단 한번의 next 이벤트를 방출하기 위해서 굳이 observable을 써야하나 의문이 생길 수 있다.&lt;/p&gt;
&lt;p&gt;이전 챕터에서 어떤 것들을 배웠는지 다시 되새겨보자. 아래 목록중 하나로 observable을 만들 수 있다고 배웠다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Observable.never() : 어떤 값도 방출하지 않고 종료되지 않는 observable sequence를 만든다.&lt;/li&gt;
&lt;li&gt;Observable.just(_:) : 하나의 값을 방출하고 completed로 종료된다.&lt;/li&gt;
&lt;li&gt;Observable.empty() : 아무 값도 방출하지 않고 completed로 종료된다.&lt;/li&gt;
&lt;li&gt;Observable.error(_) : 아무 값도 방출하지 않고 error로 종료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;목록에서 볼 수 있듯이, observable은 0개 이상의 next 이벤트를 방출한 후 completed나 error 이벤트를 방출하고 종료한다.&lt;/p&gt;
&lt;p&gt;PhotoWrite의 경우 save는 한번만 일어나기 때문에 하나의 next 이벤트를 발생시킨다. 그래서 next+completed 또는 error를 방출해야 하는데 위 목록에는 그런 조합이 없어보인다.&lt;/p&gt;
&lt;p&gt;여기서 &quot;그럼 Single은?&quot;이라고 외치면 보너스 점수가 있다. 그럼 Single은?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;RxSwift traits in practice&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;챕터2 &quot;Observable&quot;에서 RxSwift에는 특수한 경우에 쓸 수 있도록 Observable을 확장한 trait이라는 것이 있다고 배웠다.&lt;/p&gt;
&lt;p&gt;이 챕터에서는 빠른 복습을 통해 Combinestagram에 실제로 trait을 적용시켜볼 것이다. Single부터 시작하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;Single&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;챕터2에서 배웠듯이 Single은 Observable의 전문화된 케이스로, error 또는 success(Value)라는 유일한 이벤트를 방출한다. 속을 들여다 보면 success는 단지 next+completed 쌍이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;691&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH6iib/btqU4ZFjDHb/MqFyIbESFKhgMzb1eMsFo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH6iib/btqU4ZFjDHb/MqFyIbESFKhgMzb1eMsFo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH6iib/btqU4ZFjDHb/MqFyIbESFKhgMzb1eMsFo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH6iib%2FbtqU4ZFjDHb%2FMqFyIbESFKhgMzb1eMsFo1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;691&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;이런 종류의 trait은 파일을 저장하거나 불러오는 작업, 파일을 다운로드 하는 작업 같이 기본적으로 비동기적으로 값을 산출해내는 작업에 유용하다. Single의 유스케이스는 다음 두개로 나눌 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;이 챕터의 PhotoWriter.save(_:)와 같이, 성공했을 때 하나의 값만 방출하는 연산이 필요한 경우.&lt;/p&gt;
&lt;p&gt;Observable을 생성하지 않고 Single을 직접 생성할 수 있다. 사실 이 챕터의 도전과제에서 PhotoWirter의 save(_) 함수를 Single로 구현해볼 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sequence가 하나의 element만 방출하고, 2개 이상의 element를 방출했다는 것은 error라는 의도를 더욱 분명하게 만든다.&lt;/p&gt;
&lt;p&gt;그렇게 하기 위해서 observable을 구독할 때 .asSingle()을 사용해서 Single로 변환할 수 있다. 이 챕터를 다 읽고 직접 해보길 바란다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;Maybe&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;Maybe는 Single과 비슷하지만 정상적인 completed 이벤트에도 값을 방출하지 않을 수도 있다(&lt;b&gt;maybe&lt;/b&gt;)는 점이 다르다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;745&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biEwvY/btqU4A6RWvc/NdjwkkKiSksWNjFQx5SQO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biEwvY/btqU4A6RWvc/NdjwkkKiSksWNjFQx5SQO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biEwvY/btqU4A6RWvc/NdjwkkKiSksWNjFQx5SQO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiEwvY%2FbtqU4A6RWvc%2FNdjwkkKiSksWNjFQx5SQO0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;745&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;위에서 만든 사진 예시에서 Maybe가 어떻게 사용될 수 있을지 상상해보자. 애플리케이션은 자기 자신만의 커스텀 앨범을 저장한다. UserDefaults에 저장된 앨범 식별자를 계속 유지해서, 앨범을 열려고 할 때 마다 그 ID를 쓰려고 할 것이다. 이제 다음 상황들을 처리하기 위해 open(albumId:) -&amp;gt; Maybe&amp;lt;String&amp;gt; 같은 함수를 디자인할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전달된 albumID를 가진 앨범이 아직 존재할 경우, completed 이벤트를 방출한다.&lt;/li&gt;
&lt;li&gt;사용자가 그동안 앨범을 지웠다면, 새로운 앨범을 만들고 새로운 ID를 next 이벤트로 방출해서 UserDefaults에서 사용할 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;무언가 잘못되어서 더이상 사진 앨범에 접근할 수 없게 된다면 error 이벤트를 방출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다른 trait과 동일하게 &quot;기본&quot; Observable로도 위 목적을 충분히 달성할 수 있다. 하지만 Maybe를 사용하면 나중에 이 코드를 볼 다른 프로그래머들(미래의 본인도 포함)에게 더욱 명확한 의도를 알릴 수 있다.&lt;/p&gt;
&lt;p&gt;Single과 마찬가지로 Maybe도 Maybe.create({...})로 생성할 수 있고, 아니면 그냥 아무 observerble sequence를 .asMaybe()로 변환할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;Completable&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;마지막 trait은 Completable이다. 이 trait은 구독이 종료(dispose)하기 전에 단 하나의 completed 또는 error 이벤트를 방출한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;684&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4BjCU/btqUZuGkG0I/fVebMPxRKDe0gLmNLYQQj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4BjCU/btqUZuGkG0I/fVebMPxRKDe0gLmNLYQQj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4BjCU/btqUZuGkG0I/fVebMPxRKDe0gLmNLYQQj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4BjCU%2FbtqUZuGkG0I%2FfVebMPxRKDe0gLmNLYQQj0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;684&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;completed나 error 이벤트만 방출할 수 있도록 모든 next 이벤트를 무시하도록 하는 ignoreElements() operator를 통해 observable sequence를 completable로 변환할 수 있다.&lt;/p&gt;
&lt;p&gt;아니면 다른 trait을 생성할 때와 비슷한 방법으로 Completable.create { ... }을 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;아무 값도 방출하지 못하고 단지 완료나 실패만 방출할 수 있는 Completable같은 sequence를 왜 사용하는지 의문이 들 수도 있다. 하지만 비동기 작업의 성공 여부만 알 수 있는 이 기능이 사용되는 유스케이스를 보면 그 개수에 놀라게 될 것이다.&lt;/p&gt;
&lt;p&gt;Combinestagram으로 돌아가기 전에 한가지 예제를 보자. 사용자가 작업중인 와중에 앱이 도큐먼트를 자동저장한다고 가정해보자. 백그라운드에서 돌아가는 비동기 저장이 성공하면 작은 알림창을 띄우거나 실패했을 때 애러창을 띄울 수 있다.&lt;/p&gt;
&lt;p&gt;saveDocument() -&amp;gt; Completable에 자동저장 로직이 구현되어 있다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611823305361&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;saveDocument()
  .andThen(Observable.from(createMessage))
  .subscribe(onNext: { message in
    message.display()
  }, onError: {e in
    alert(e.localizedDescription)
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;andThen operator를 사용하면 성공 이벤트에 더 많은 completable이나 observable을 연결해서 최종 결과값을 구독할 수 있다. 위 코드의 경우 saveDocument()로 생성된 Completable이나 andThen으로 추가한 Observable중 아무나 error 이벤트를 방출해도 onError closure에서 처리된다.&lt;/p&gt;
&lt;p&gt;앞으로 나올 두 챕터에서 Completable을 사용하게 될 것이다. 지금은 Combinestagram으로 돌아가자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;Subscribing to your custom observable&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;현재 기능(사진을 사진 라이브러리에 저장)은 trait의 유스케이스 중 하나이다. PhotoWriter.save(_)는 단 한번 방출하거나(새로운 asset ID) error를 방출하기 때문에 Single을 사용하기에 아주 적합하다.&lt;/p&gt;
&lt;p&gt;이제 재미있는 일만 남았다. 임의의 Observable을 만들어서 기존의 고지식한 것들을 날려버리자!&lt;/p&gt;
&lt;p&gt;&lt;b&gt;MainViewController.swift&lt;/b&gt;를 열고 actionSave()를 찾아서 안에 &lt;b&gt;Save&lt;/b&gt; 버튼을 위한 다음 코드를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611823325968&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;guard let image = imagePreview.image else { return }

PhotoWriter.save(image)
  .asSingle()
  .subscribe(
    onSuccess: { [weak self] id in
      self?.showMessage(&quot;Saved with id: \(id)&quot;)
      self?.actionClear()
    },
    onError: { [weak self] error in
      self?.showMessage(&quot;Error&quot;, description: error.localizedDescription)
    }
  )
  .disposed(by: bag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 현재 사진모음을 저장하기 위해 PhotoWriter.save(image)를 호출했다. 그리고 반환된 Observable을 하나의 element만 방출하는 Single로 변환해서 성공, 실패 결과를 화면에 출력하도록 했다. 추가로 작업이 성공적으로 끝나면 현재 목록을 초기화 시켰다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;: asSingle()은 sequence가 2개 이상의 element를 방출하려고 하면 error로 처리하도록 보장한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;마지막으로. 완성된 애플리케이션을 실행해서 멋진 사진 모음을 만들어 저장해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;394&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvsczR/btqU3pxP1iX/vXoXoD4mG90OBh61gMKY51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvsczR/btqU3pxP1iX/vXoXoD4mG90OBh61gMKY51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvsczR/btqU3pxP1iX/vXoXoD4mG90OBh61gMKY51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvsczR%2FbtqU3pxP1iX%2FvXoXoD4mG90OBh61gMKY51%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;394&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;저장된 결과는 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;사진&lt;/span&gt;앱에서 확인할 수 있다는 것을 잊지말자!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;427&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ7Mw1/btqU3pYOcvT/kBAUlUdIDsR5vJTfdHIE60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ7Mw1/btqU3pYOcvT/kBAUlUdIDsR5vJTfdHIE60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ7Mw1/btqU3pYOcvT/kBAUlUdIDsR5vJTfdHIE60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ7Mw1%2FbtqU3pYOcvT%2FkBAUlUdIDsR5vJTfdHIE60%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;427&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;이렇게 Section 1을 마무리 했다.(축하합니다!)&lt;/p&gt;
&lt;p&gt;이제 RxSwift 세계의 파다완 단계는 지났지만 아직 제다이가 되기는 멀었다. 너무 느리다고 다크 사이드에 빠지지는 말아라. 아직 네트워크, 스레드, 애러 처리 등과의 전투가 남아있다.&lt;/p&gt;
&lt;p&gt;그러기 전에 RxSwift의 가장 강력한 면을 훈련해야 한다. Section 2 &quot;Operators and Best Practices&quot;에서 다룰 operator는 Observable에게 슈퍼파워를 부여하고 신세계를 맞보여 줄 것이다.&lt;/p&gt;</description>
      <category>Programming/RxSwift</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/234</guid>
      <comments>https://eastroot1590.tistory.com/entry/RxSwift-Beginner-Objservables-Subjects-in-Practice%EB%B2%88%EC%97%AD#entry234comment</comments>
      <pubDate>Thu, 28 Jan 2021 17:43:23 +0900</pubDate>
    </item>
    <item>
      <title>RxSwift Beginner | Subject(번역)</title>
      <link>https://eastroot1590.tistory.com/entry/RxSwift-Beginner-Subject%EB%B2%88%EC%97%AD</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://www.raywenderlich.com/books/rxswift-reactive-programming-with-swift/v4.0/chapters/3-subjects&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;원본 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 observable이 무엇인지, 어떻게 생성하는지, 어떻게 구독하는지, 그리고 작업이 끝났을 때 어떻게 구독을 취소하는지 알게 되었을 것이다. observable은 RxSwift의 가장 기초적인 부분이지만 본질적으로 읽기만 가능하다. 구독을 통해 observable이 방출하는 이벤트를 받기만 할 수 있다는 뜻이다.&lt;/p&gt;
&lt;p&gt;일반적으로 애플리케이션을 개발할 때, 런타임 중에 observable에 어떤 값을 추가하고 구독자들에게 값을 방출해야 한다. observable과 observer 두가지 기능이 모두 필요하고 이것이 바로 &lt;b&gt;subject&lt;/b&gt;다.&lt;/p&gt;
&lt;p&gt;이 챕터에서는 RxSwift가 가지고 있는 여러 종류의 subject에 대해 배울 것이고, 각각이 어떻게 동작하는지, 그리고 어떤 경우에 각 subject를 선택해야 하는지 배우게 될 것이다. 그리고 subject의 wrapper인 &lt;b&gt;relay&lt;/b&gt;에 대해서도 배우게 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;이 챕터에서 사용할 프로젝트의 RxPlayground 폴더에 있는 ./bootstrap.sh를 실행하고 RxSwiftPlayground를 선택한다. 간단한 예제부터 시작해보자. playground에 다음 코드를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575585096&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;PublishSubject&quot;) {
	let subject = PublishSubject&amp;lt;String&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;PublishSubject&lt;/span&gt;는 신문 출판사(newpaper publisher)처럼 정보를 받아서 구독자들에게 전달하기 때문에 적절한 네이밍이라고 볼 수 있다. String 타입으로 만들었기 때문에 오직 문자열만 받아서 전달할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575593995&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.on(.next(&quot;Is anyone listening?&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;subject에 새로운 문자열을 입력했다. 아직 구독자(subscriber)가 아무도 없기 때문에 콘솔에는 아무것도 출력되지 않는다. 구독자를 하나 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575603022&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let subscriptionOne = subject
	.subscribe(onNext: {string in
		print(string)
	})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이전 챕터에서 osbservable에 했던것과 동일한 방법으로 subject를 subscribe한 후 next 이벤트의 element를 출력했다. 하지만 여전히 콘솔에 아무것도 출력되지 않는다. 왜 그럴까?&lt;/p&gt;
&lt;p&gt;PublishSubject는 observable과 다르게 &lt;b&gt;현재&lt;/b&gt; 구독자에게만 이벤트를 방출한다. 때문에 이벤트를 생성할 때 구독하고 있지 않으면 아무 이벤트도 받을 수 없다. 벌목 사업을 생각해보자. 만약 숲에 아무도 없어서 나무가 쓰러지는 소리를 아무도 들을 수 없다면 불법 벌목사업을 성공시킬 수 있을까?&lt;/p&gt;
&lt;p&gt;예제 마지막에 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575615630&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.on(.nect(&quot;1&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;publish subject를 만들 때 string 타입으로 만들었기 때문에 String만 추가할 수 있다는 것을 기억하자. 이번에는 subject에 구독자가 있기 때문에 값이 방출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575636391&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: PublishSubject ---
1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;subscribe operator가 구독자를 추가하는 것 처럼 on(.next(_:))는 subject에 값을 방출할 수 있는, 새로운 next 이벤트를 추가한다. 그리고 subscribe와 동일하게 간소화된 표현식을 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575645047&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.onNext(&quot;2&quot;) &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;onNext(_:)는 on(.next(_:))와 가독성에서 차이가 있을 뿐 같은 동작을 한다. 이제 실행결과에 2도 출력되는 것을 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575653742&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: PublishSubject ---
1
2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 subject의 대해 조금 더 깊이 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;What are subjects?&lt;/h2&gt;
&lt;p&gt;Subject는 observable과 observer 두가지 모두의 기능을 포함한 것처럼 동작한다. 앞서 이들이 어떻게 이벤트를 방출하고 구독할 수 있는지 알아보았다. 위 예제에서 subject는 next 이벤트를 받고, 이 이벤트는 각 subscriber에게 방출했다.&lt;/p&gt;
&lt;p&gt;RxSwift는 네가지 subject type을 제공한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PublishSubject: 비어있는 상태로 생성되며 새로운 element만 방출한다.&lt;/li&gt;
&lt;li&gt;BehaviorSubject: 기본값을 가진 상태로 생성되며, 초기값 또는 최신 element를 방출한다.&lt;/li&gt;
&lt;li&gt;ReplaySubject: 정해진 크기의 buffer를 가진 채로 생성되며 새 구독자에게 생성된다.&lt;/li&gt;
&lt;li&gt;AsyncSubject: completed 이벤트가 방출될 때만 마지막 next 이벤트만 방출한다. 그다지 많이 사용하지 않는 subject이며 이 책에서도 사용하지 않을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RxSwift는 &lt;b&gt;Relay&lt;/b&gt;라는 것도 제공하는데 각 PublishSubject와 BehaviorSubject를 감싸면서(wrap) next 이벤트만 받을 수 있는 PublishRelay와 BehaviorRelay도 제공한다. relay는 error나 completed 이벤트를 받을 수 없기 때문에 종료되지 않고 계속 살아있어야 하는 sequence를 만들 때 유용하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note&lt;/b&gt;: 이번 챕터의 예제를 작성하면서 import RxRelay를 추가해야 한다는 에러를 확인했는가? 원래 relay는 RxCocoa의 한 부분으로 작성되었지만 relay는 Cocoa framework을 사용하지 않는 부분에서도 범용적으로 쓰일 수 있기 때문에 RxRelay라는 모듈이 따로 존재한다.&lt;/blockquote&gt;
&lt;p&gt;이어서 subject와 relay를 어떻게 다루는지 알아볼 것이다. subject를 publish하는 것 부터 시작하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Working with publish subject&lt;/h2&gt;
&lt;p&gt;publish subject는 구독자가 구독을 한 순간부터, 구독을 취소하거나 sequence가 종료될 때 까지 계속 최신 이벤트를 받도록 만드는 데 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;marble diagram에서 제일 위에있는 줄은 publish subject이며, 두번째 세번째 줄은 구독자이다. 위로 향하는 점선은 구독(subscribe)을, 아래로 향하는 점선은 이벤트의 방출을 뜻한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;414&quot; data-filename=&quot;publish subject.png&quot; width=&quot;648&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAMiuX/btqUL9uQhqL/1djnMCjDMHkEuwLoECFUD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAMiuX/btqUL9uQhqL/1djnMCjDMHkEuwLoECFUD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAMiuX/btqUL9uQhqL/1djnMCjDMHkEuwLoECFUD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAMiuX%2FbtqUL9uQhqL%2F1djnMCjDMHkEuwLoECFUD1%2Fimg.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;414&quot; data-filename=&quot;publish subject.png&quot; width=&quot;648&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;첫번째 구독자는 1이 방출된 후에 구독을 시작했기 때문에 1은 받지 못하고 2, 3을 받았다. 두번째 구독자는 2가 방출된 후에 구독을 시작했기 때문에 3만 받았다.&lt;/p&gt;
&lt;p&gt;playground로 돌아와서 아래 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575699979&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let subscriptionTwo = subject
	.subscribe {event in
		print(&quot;2)&quot;, event.element ?? event)
	})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;event는 next일 때 방출되는 값을 element라는 optional로 가지고 있기 때문에 nil-coalescing으로 값을 얻어서 출력할 수 있다.&lt;/p&gt;
&lt;p&gt;1, 2가 방출된 후에 subscribe 했기 때문에 subscriptionTwo는 아무런 출력을 하지 않는다. 이제 새로운 이벤트를 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575708841&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suject.onNext(&quot;3&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 3은 subscriptionOne, subscriptionTwo에 의해 두번 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575716677&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;3
2) 3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번에는 subscriptionOne의 구독을 취소하고 새로운 이벤트를 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575725309&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subscriptionOne.dispose()
subject.onNext(&quot;4&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4는 subscriptionTwo에게만 방출되어 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575734119&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2) 4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;publish subject는 completed나 error 이벤트, 합쳐서 stop 이벤트라고 부르는 이 이벤트들 중 하나를 받아서 종료되면 더이상 새로운 이벤트를 방출하지 않는다. 하지만 새로운 subscriber가 생기면 이 마지막 stop 이벤트를 방출한다. 아래 코드를 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575744243&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
subject.onCompleted()

// 2
subject.onNext(&quot;5&quot;)

// 3
subscriptionTwo.dispose()

let disposeBag = DisposeBag()

// 4
subject
	.subscribe {
		print(&quot;3)&quot;. $0.element ?? $0)
	}
	.disposed(by: disposeBag)

subject.onNext(&quot;?&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;completed 이벤트를 추가해서 subject를 종료시킨다.&lt;/li&gt;
&lt;li&gt;또다른 next 이벤트를 subject에 추가한다. 이미 종료되었기 때문에 이 이벤트는 방출되지 않을 것이다.&lt;/li&gt;
&lt;li&gt;subscriptionTwo의 구독을 해제한다.&lt;/li&gt;
&lt;li&gt;subject를 구독하는 새로운 구독자를 추가한다. 이번에는 dispose bag을 사용해서 dispose 시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 세번째 구독자는 어떤 이벤트를 받을까? observable에서 배운대로 라면 아무 이벤트도 받지 않을 것이라고 예상할 수 있지만 세번째 구독자는 completed 이벤트를 받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575753811&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2) completed
3) completed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;subject는 종료된 후에도 새로운 구독자에게 자신이 종료되었음을 알린다. 현재 구독자 뿐 아니라 새로운 구독자에게도 종료 이벤트를 방출하기 때문에 sequence의 종료를 조금 더 명확하게 알릴 수 있지만 때때로 이 기능은 버그를 유발할 수 있기 때문에 조심해야 한다.&lt;/p&gt;
&lt;p&gt;publish subject는 입찰 시스템과 같이 시간에 민감한 시스템을 구현할 때 사용할 수 있다. 10시 1분에 가입한 사용자에게 9시 59분에 경매가 1분밖에 남지않았다는 알림을 보내서 화가 난 사용자가 별점 1점을 주는 것을 방지할 수 있다.&lt;/p&gt;
&lt;p&gt;때때로 새롭게 구독을 시작한 사용자가 가장 최근에 방출된 element를 알아야 하는 경우가 있는데, publish subject는 새로운 구독자에게 값을 전달하지 않기 때문에 &quot;사용자가 선택한 항목&quot;, &quot;방금 도착한 알림&quot;과 같은 이벤트를 모델링하는 데 적합하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Working with behavior subjects&lt;/h2&gt;
&lt;p&gt;behavior subject는 publish subject와 비슷하게 동작하지만 이들은 가장 최근에 발생한 next 이벤트도 새로운 구독자에게 방출한다. marble diagram을 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;behavior subject.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;414&quot; width=&quot;620&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fd3G7/btqUEAUxeXr/TfI18gPoKILrszc4HbIeTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fd3G7/btqUEAUxeXr/TfI18gPoKILrszc4HbIeTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fd3G7/btqUEAUxeXr/TfI18gPoKILrszc4HbIeTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFd3G7%2FbtqUEAUxeXr%2FTfI18gPoKILrszc4HbIeTk%2Fimg.png&quot; data-filename=&quot;behavior subject.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;414&quot; width=&quot;620&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;첫번째 줄은 subject를 뜻한다. 두번째 줄로 표현된 첫번째 구독자는 1이 방출된 후에 구독을 시작했고 가장 최근에 방출된 element인 1부터 받을 수 있다. 같은 방법으로 두번재 구독자는 2가 방출된 후에 구독을 시작했지만 구독을 요청했을 때 2도 받고 순서대로 3을 받을 수있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575783253&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
enum MyError: Error {
	case anError
}

// 2
func print&amp;lt;T: CustomStringConvertible&amp;gt;(label: String, event: Event&amp;lt;T&amp;gt;) {
	print(label, (event.element ?? event.error) ?? event)
}

// 3
example(of: &quot;BehaviorSubject&quot;) {
	// 4
	let subject = BehaviorSubject(value: &quot;initial value&quot;)
	let disposeBag = DisposeBag()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;error를 정의한다.&lt;/li&gt;
&lt;li&gt;event 정보를 출력할 수 있도록 print 함수를 확장한다.&lt;/li&gt;
&lt;li&gt;새로운 예제 함수를 작성한다.&lt;/li&gt;
&lt;li&gt;BehaviorSubject를 만들면서 initial value로 초기화 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note&lt;/b&gt;: BehaviorSubject는 항상 최근 element를 방출하기 때문에 initial value를 필요로 한다. 초기값을 정할 수 없는 경우에는 BehaviorSubject 대신 PublishSubject를 사용하거나 optional로 초기화 할 수 있다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1611575811837&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject
	.subscribe {
		print(label: &quot;1)&quot;, event: $0)
	}
	.disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;subject를 만들고 곧바로 구독했기 때문에 초기값이 방출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575821994&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: BehaviorSubject ---
1) Initial value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 behavior subject를 구독하기 전에 이벤트를 추가하는 코드를 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575831025&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.onNext(&quot;X&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 구독하기 전 가장 최근 값이 X가 되었기 때문에 X가 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575839591&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: BehaviorSubject ---
1) X &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아래 코드를 추가하고 실행하기 전에 어떤 값이 출력될지 예상해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575848948&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
subject.onError(MyError.anError)

// 2
subject
	.subscribe {
		print(label: &quot;2)&quot;, event: $0)
	}
	.disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;subject에 error 이벤트를 추가했다.&lt;/li&gt;
&lt;li&gt;새로운 구독을 추가했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1611575858151&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1) anError
2) anError&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예상이 맞았는가? error는 각 구독자에게 모두 방출되어 출력된다.&lt;/p&gt;
&lt;p&gt;behavior subject는 최신 데이터로 화면을 그려야 하는 경우에 유용하게 사용할 수 있다. 예를 들어 유저 프로필 화면을 behavior subject로 연결해서 프로필 정보가 바뀔 때 마다 새로운 정보로 패치하도록 만들 수 있다.&lt;/p&gt;
&lt;p&gt;behavior subject는 가장 최신 정보를 전송(replay)하기 때문에 &quot;요청 처리중&quot; 또는 &quot;현재 시간은 9시 41분&quot;과 같이 상태를 모델링 하는 데 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;가장 최신정보 보다 더 많은 정보를 얻고싶을 때는 어떻게 해야 할까? 예를 들어 검색화면은 가장 최근에 검색했던 항목 뿐 아니라 이전에 검색했던 내용들을 보여줄 수 있어야 한다. 이제 replay subject가 등장할 차례이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Working with replay subjects&lt;/h2&gt;
&lt;p&gt;Replay subject는 초기에 설정한 크기의 버퍼에 일시적으로 자신이 최근에 방출했던 element를 저장한다. 그리고 버퍼에 있는 element들을 새로운 구독자에게 전송한다.&lt;/p&gt;
&lt;p&gt;버퍼의 크기가 2인 marble diabram을 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;replay subject.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;414&quot; width=&quot;629&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CJCTs/btqUvDLweqy/U08Xpgs47KH5szTkxxsQg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CJCTs/btqUvDLweqy/U08Xpgs47KH5szTkxxsQg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CJCTs/btqUvDLweqy/U08Xpgs47KH5szTkxxsQg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCJCTs%2FbtqUvDLweqy%2FU08Xpgs47KH5szTkxxsQg0%2Fimg.png&quot; data-filename=&quot;replay subject.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;414&quot; width=&quot;629&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;첫번째 구독자(두번째 줄)는 이미 replay subject(첫번째 줄)을 구독하고 있기 때문에 방출되는 모든 element를 받는다. 두번째 구독자(세번째 줄)은 2가 방출된 뒤에 구독했지만 이전에 방출되었던 1, 2,를 모두 받는다.&lt;/p&gt;
&lt;p&gt;replay subject를 사용할 때 버퍼는 항상 메모리를 사용한다는 점을 기억하자. replay subject에 image처럼 크기가 큰 element를 사용한다면 그만큼 큰 크기의 메모리 버퍼가 필요할 것이다.&lt;/p&gt;
&lt;p&gt;또 주의해야 할 점은 replay subject를 배열로 생성하는 것이다. 각 배열에 대해 버퍼를 생성해야 하기 때문에 메모리 사용이 그만큼 많아지게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575887338&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;ReplaySubject&quot;) {
	// 1
	let subject = ReplaySubject&amp;lt;String&amp;gt;.create(bufferSize: 2)
	let disposeBag = DisposeBag()

	// 2
	subject.onNext(&quot;1&quot;)
	subject.onNext(&quot;2&quot;)
	subject.onNext(&quot;3&quot;)

	// 3
	subject
		.subscribe {
			print(label: &quot;1)&quot;, event: $0)
		}
		.disposed(by: disposeBag)

	subject
		.subscribe {
			print(label: &quot;2)&quot;, event: $0)
		}
		.disposed(by: disposeBag)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;버퍼 크기가 2인 replay subject를 생성한다. replay subject는 create(bufferSize:) 함수를 통해 만들 수 있다.&lt;/li&gt;
&lt;li&gt;세개의 element를 추가한다.&lt;/li&gt;
&lt;li&gt;두개의 구독자를 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;버퍼 크기가 2이기 때문에 가장 처음 입력된 1은 방출되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575896946&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: ReplaySubject ---
1) 2
1) 3
2) 2
2) 3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음, 아래 코드를 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575910698&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.onNext(&quot;4&quot;)

subject
	.subscribe {
		print(label: &quot;3)&quot;, event: $0)
	}
	.disposed(by: disposedBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 네번재 element를 추가했다. 그리고 새로운 구독자를 만들어서 구독하도록 했다. 처음 두 구독자는 이미 구독을 하고있는 상태이기 때문에 정상적으로 4를 받을 수 있지만 새로운 구독자의 경우 버퍼에 있는 최근 element를 모두 받게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575919311&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1) 4
2) 4
3) 3
3) 4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아직까지 잘 작동하는 것 처럼 보인다. 하지만 error를 발생시켜보면 어떻게 될까? 세번째 구독을 추가하는 코드 바로 위에 이 코드를 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575926894&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.onError(MyError.anError)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1611575943414&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1) 4
2) 4
1) anError
2) anError
3) 3
3) 4
3) anError&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;error 이벤트는 sequence를 종료하고 더이상 값을 방출하지 않게 한다고 배웠다. 하지만 버퍼가 가지고 있는 3, 4가 그대로 세번재 구독자에게 방출된다.&lt;/p&gt;
&lt;p&gt;다음 코드를 error 다음에 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611575951351&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subject.dispose()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명시적으로 dispopse()를 호출해서 subject를 강제로 종료시켰다. 새로운 구독자는 subject가 이미 dispose되었다는 에러만 받게 될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575959040&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;3) Object 'RxSwift...ReplayMany&amp;lt;Swift.String&amp;gt;' was already disposed.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이처럼 명시적으로 dispose()를 호출하는 것은 일반적인 방법은 아니다. dispose bag에 추가하면 dispose bag이 owner(view model이나 view controller)에 의해 해제될 때 자동으로 dispose된다.&lt;/p&gt;
&lt;p&gt;이러한 경계에 대해 항상 조심하는 것이 좋다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note&lt;/b&gt;: RelayMany가 뭔지 궁금할 수 있다. RelayMany는 replay subject를 생성하는 내부적인 타입이다.&lt;/blockquote&gt;
&lt;p&gt;publish, behavior, replay subject를 통해 대부분의 모델을 구현할 수 있다. 하지만 올드스쿨로 돌아가서 observable에게 현재 가지고 있는 값을 물어보고 싶을 때도 있다. 이럴 때를 위해 Relay가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Working with relays&lt;/h2&gt;
&lt;p&gt;앞서 relay는 element를 전달하는 기능을 유지한 채 subject를 감싸는(wrap) 것이라고 배웠다. 일반적인 observable을 포함한 다른 subject와 다르게, relay에 값을 추가하려면 accept(:) 함수를 사용해야 한다. 다시말해 onNext(:)를 사용하지 않는다. 그 이유는 relay는 error나 completed 이벤트를 방출할 수 없고 단지 값을 받기만 할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;PublishRelay는 PublishSubject를 감싸고, BehaviorRelay는 BehaviorSubject를 감싼다. 차이점은 relay는 절대 종료되지 않는다는 것을 보장한다는 점이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575978581&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;PublishRelay&quot;) {
	let relay = PublishRelay&amp;lt;String&amp;gt;()
	
	let disposeBag = DisposeBag()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이름만 빼면 PublishSubject를 생성하는 것과 다른점이 없다. 하지만 relay에 새로운 값을 추가하기 위해서 accept(_:)를 사용해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611575992633&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;relay.accept(&quot;Knock knock, anyone home?&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아직 구독자를 만들지 않았기 때문에 아무것도 방출되지 않는다. 구독자를 만들고 또다른 값을 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611576007528&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;relay
	.subscribe {onNext: {
		print($0)
	})
	.disposed(by: disposeBag)

relay.accept(&quot;1&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과는 subject를 사용했을 때와 동일하다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576016063&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: PublishRelay ---
1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 relay는 error나 completed 이벤트를 방출할 수 없기 때문에 subject에서 사용했던 다음 코드들은 컴파일 에러를 발생시킨다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576025121&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;relay.accept(MyError.anError)
relay.onCompleted()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;publish relay는 publish subject를 감싸고, accept와 절대 끝나지 않는다는 점을 빼면 똑같이 작동한다는 것을 기억하자.&lt;/p&gt;
&lt;p&gt;Behavior relay도 마찬가지로 completed나 error가 없어서 종료되지 않는다. behavior subject를 감싸기 때문에 생성할 때 초기값이 있어야 하며 구독자에게 초기값 또는 가장 최신 값을 전달한다. behavior relay의 특별한 점은 언제든지 현재 최신 값을 물어볼 수 있다는 점이다. 이 기능은 기존 프로그래밍과 reactive 프로그래밍을 결합한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576034384&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;BehaviorRelay&quot;) {
	// 1
	let relay = BehaviorRelay(value: &quot;Initial value&quot;)
	let disposeBag = DisposeBag()

	// 2
	relay.accept(&quot;New initial value&quot;)

	// 3
	relay
		.subscribe {
			print(label: &quot;1)&quot;, event: $0)
		}
		.disposed(by: disposeBag)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;behavior relay를 생성한다. 초기값을 통해 String으로 추론되지만 명시적으로 타입을 지정할 수도 있다.&lt;/li&gt;
&lt;li&gt;relay에 새로운 값을 추가한다.&lt;/li&gt;
&lt;li&gt;relay를 구독한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1611576045812&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: BehaviorRelay ---
1) New initial value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576055040&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
relay.accept(&quot;1&quot;)

// 2
relay
	.subscribe {
		print(label: &quot;2)&quot;, event: $0)
	}
	.disposed(by: disposeBag)

// 3
relay.accept(&quot;2&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;relay에 새로운 값을 추가한다.&lt;/li&gt;
&lt;li&gt;relay에 새로운 구독을 추가한다.&lt;/li&gt;
&lt;li&gt;또다른 값을 relay에 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;기존에 구독을 하고있던 1)은 새로운 값 &quot;1&quot;을 받고, 이 값이 가장 최신 값이기 때문에 새로운 구독자도 구독을 할 때 같은 값을 받는다. 그리고 두 구독자 모두 두번째 값 &quot;2&quot;를 받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576065269&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1) 1
2) 1
1) 2
2) 2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마지막으로 현재 relay가 가지고 있는 값을 출력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576074495&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(relay.value)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;behavior relay는 현재 가지고 있는 값에 직접 접근할 수 있다. 이 경우에 가장 최신값인 &quot;2&quot;가 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611576083993&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 기능은 reactive 프로그래밍과 기존 reactive가 아닌 방식을 연결하는 데 굉장히 유용하다.&lt;/p&gt;
&lt;p&gt;behavior relay는 다양한 부분에 쓰인다. 다른 subject와 같이 새로운 값에 반응하기 위해 behavior relay를 구독해도 되고, 구독까지 할 필요는 없고 단지 현재 값을 얻어올 필요가 있을 때도 value로 구독 없이 값을 얻어올 수 있다.&lt;/p&gt;</description>
      <category>Programming/RxSwift</category>
      <category>rxswift</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/233</guid>
      <comments>https://eastroot1590.tistory.com/entry/RxSwift-Beginner-Subject%EB%B2%88%EC%97%AD#entry233comment</comments>
      <pubDate>Mon, 25 Jan 2021 21:02:08 +0900</pubDate>
    </item>
    <item>
      <title>RxSwift Beginner | Observable(번역)</title>
      <link>https://eastroot1590.tistory.com/entry/RxSwift-Beginner-Observable%EB%B2%88%EC%97%AD</link>
      <description>&lt;p&gt;맥북을 중고로 판매하고 새로운 구매해서 기다리고 있는 관계로 직접 코딩을 할 수 없게 되었다. 배송을 기다리면서 RxSwift 문서를 번역해보기로 했다. 번역은 처음이라 문장이 너무 어색한데 하다보면 점점 괜찮아지지 않을까?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.raywenderlich.com/books/rxswift-reactive-programming-with-swift/v4.0/chapters/2-observables&quot;&gt;원문 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 RxSwift에 대해 기본적인 컨셉을 배웠다면, 이제는 observable을 배울 차례다.&lt;/p&gt;
&lt;p&gt;이 챕터에서 observable을 생성(create)하고 구독(subscribe)하는 몇가지 예제를 다룰 것이다. 일부 사용법은 실세계에서 모호하게 느껴질 수 있지만 observable을 다루는 중요한 스킬과 수많은 활용성을 배울 수 있을 것이다. 이 기술을 앞으로 사용하게 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;이 챕터에서는 RxSwift framework와 playgound가 포함된 Xcode 프로젝트를 사용할 것이다. 터미널을 열고 프로젝트 폴더의 &lt;b&gt;RxPlaygound&lt;/b&gt; 프로젝트 폴더로 이동한다. 끝으로, bootstrap.sh 스크립트를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493303983&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./bootstrap.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행하는데 시간이 조금 걸릴 수 있다. 앞으로 이 playground 프로젝트를 열 때 마다 위 과정을 반복해야 한다는 것을 잊지말자.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Project navigator&lt;/b&gt;에서 &lt;b&gt;RxSwiftPlayground&lt;/b&gt; 프로젝트를 선택하면 아래와 같은 화면을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Projcet navigator&lt;/b&gt;에서 &lt;b&gt;Sources&lt;/b&gt;를 통해 playground 페이지를 열고 &lt;b&gt;SupportCode.swift&lt;/b&gt;를 선택한다. 파일은 간단한 테스트 closure를 실행할 수 있는 다음 함수가 작성되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493315907&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func example(of description: String, action: () -&amp;gt; Void) {
	print(&quot;\n--- Example of:&quot;, description, &quot;---&quot;)
	action()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;앞으로 나오게 될 예제들을 이 함수를 사용해서 캡슐화 할 것이다. 이 함수를 약식으로 사용하는 방법도 배울 것이다.&lt;/p&gt;
&lt;p&gt;하지만 그 전에, 짚고 넘어가야 할 문제가 있다: observable이 대체 뭐야?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;What is an observable?&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;Observable&lt;/b&gt;은 Rx의 심장과도 같다. 이 챕터에서 observable이 무엇인지, 어떻게 생성하는지, 그리고 어떻게 사용하는지에 대해 공부하게 될 것이다.&lt;/p&gt;
&lt;p&gt;Rx에서 &quot;observalbe&quot;, &quot;observable sequence&quot;, &quot;sequence&quot;와 같은 용어들을 볼 수 있는데 이것들은 모두 같은 의미이다. 종종 &quot;stream&quot;이라는 단어가 다른 reactive programming 환경으로부터 넘어와서 사용되는 것을 볼 수도 있는데 &quot;stream&quot;도 같은 의미이다. 하지만 RxSwift에서는 이 모두를 &lt;b&gt;sequence&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;p&gt;observable은 특별한 능력을 가진 sequence다. 그 중 하나가 바로(사실 가장 중요한) **비동기성(asynchronous)**이다. observable은 **방출(emitting)**이라고 하는 이벤트를 일정 기간동안 생성한다. 이벤트는 값, custom type의 instance 또는 tap같은 gesture recognizer를 포함할 수 있다.&lt;/p&gt;
&lt;p&gt;이걸 설명하는 가장 좋은 방법 중 하나는 시간의 흐름에 따라 값을 단순히 값을 기록한 &lt;b&gt;marble diagram&lt;/b&gt;을 이용하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;marble diagram.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;107&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhafa/btqUwyPHzkm/gNQ8BH0iDB1lKEuIEtmC00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhafa/btqUwyPHzkm/gNQ8BH0iDB1lKEuIEtmC00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhafa/btqUwyPHzkm/gNQ8BH0iDB1lKEuIEtmC00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frhafa%2FbtqUwyPHzkm%2FgNQ8BH0iDB1lKEuIEtmC00%2Fimg.png&quot; data-filename=&quot;marble diagram.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;107&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;왼쪽에서 오른쪽으로 향하는 화살표는 시간을 나타낸다. 숫자가 쓰여진 원은 이 sequence의 값(element)를 뜻한다. 시간의 흐름에 따라 1, 2, 3이 순서대로 방출된다. 얼만큼의 시간간격 마다 값이 방출되는지 궁금할 수 있다. 답은 observable의 생명주기 안에서 언제든지 발생할 수 있다. 이제 자연스럽게 다음 장에서 observable의 생명주기에 대해 배울 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Lifecycle of an observable&lt;/h2&gt;
&lt;p&gt;이전에 본 marble diagram에서 observable은 3개의 값을 방출했다. observable이 값을 방출할 때 &lt;b&gt;next&lt;/b&gt;라는 이벤트를 호출한다.&lt;/p&gt;
&lt;p&gt;이번에는 observable의 끝을 뜻하는 세로줄이 있는 diagram을 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;completed.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;108&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kL9GV/btqUsd0oCZS/GHBdSR1rWG69FN5K21tSDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kL9GV/btqUsd0oCZS/GHBdSR1rWG69FN5K21tSDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kL9GV/btqUsd0oCZS/GHBdSR1rWG69FN5K21tSDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkL9GV%2FbtqUsd0oCZS%2FGHBdSR1rWG69FN5K21tSDk%2Fimg.png&quot; data-filename=&quot;completed.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;108&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;이 observable은 세번의 tap 이벤트를 방출하고 끝난다. 끝은 &lt;b&gt;completed&lt;/b&gt; 이벤트를 호출하고 이는 **종료(terminated)**를 뜻한다. 예를 들어, tap을 할 수 있는 view가 사라지는 경우가 있다. 중요한 점은 observable은 끝났고, 더이상 어떤 것도 방출할 수 없다는 점이다. 이 경우 정상적인 종료라고 할 수 있지만 어떤 경우에는 일이 잘못될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;error.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;107&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MeGLK/btqUsdTBbYE/p6MyRoOIK2Q9EKjwH7HGR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MeGLK/btqUsdTBbYE/p6MyRoOIK2Q9EKjwH7HGR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MeGLK/btqUsdTBbYE/p6MyRoOIK2Q9EKjwH7HGR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMeGLK%2FbtqUsdTBbYE%2Fp6MyRoOIK2Q9EKjwH7HGR1%2Fimg.png&quot; data-filename=&quot;error.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;107&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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&gt;오류는 빨간 x로 표기한다. observable은 error와 함께 &lt;b&gt;error&lt;/b&gt; 이벤트를 호출한다. observable이 error를 방출하면 completed로 종료되는 것과 동일하게 어떤 것도 방출할 수 없게 된다.&lt;/p&gt;
&lt;p&gt;요약:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;observable은 &lt;b&gt;next&lt;/b&gt;이벤트로 값을 방출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;completed&lt;/b&gt;나 &lt;b&gt;error&lt;/b&gt;이벤트로 종료될 때 까지 지속된다.&lt;/li&gt;
&lt;li&gt;한 번 종료된 observable은 더이상 어떤 것도 방출할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이벤트는 enum case로 표현한다. 실제 RxSwift의 이벤트 코드를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611493371731&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Event&amp;lt;Element&amp;gt; {
	case next(Element)
	case error(Swift.Error)
	case completed
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;next이벤트는 Element를 전달할 수 있고, error는 Swift.Error를 전달할 수 있다. completed는 단순히 종료를 알리고, 아무 데이터도 전달하지 않는다.&lt;/p&gt;
&lt;p&gt;이제 observable이 무엇인지 알아보았으니 실제로 몇가지 observable을 만들어서 어떻게 동작하는지 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Creating observable&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;RxSwiftPlayground&lt;/b&gt;로 이동해서 아래 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493388627&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;just, of, from&quot;) {
	// 1
	let one = 1
	let two = 2
	let three = 3

	// 2
	let observable = Observable&amp;lt;Int&amp;gt;.just(one)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;예제에서 사용할 Int를 정의한다.&lt;/li&gt;
&lt;li&gt;just를 사용해서 one이라는 상수를 가진 Int형 observable sequence를 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;just&lt;/b&gt;는 Observable의 static method로, 단지(just) 하나의 값을 가질 수 있는 observable sequence를 생성하기 때문에 아주 적합한 이름이다. 하지만 Rx에서 method는 &lt;b&gt;operator&lt;/b&gt;라고 부른다. 다음은 &lt;b&gt;of&lt;/b&gt; operator에 대해 알아볼 것이다.&lt;/p&gt;
&lt;p&gt;같은 코드에 다음을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493403111&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let observable2 = Observable.of(one, two, three) &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번에는 자료형을 정의하지 않았다. Int를 여러개 전달했기 때문에 [Int]로 추론되었을 것이라고 생각할 수도 있다. 하지만 생성된 observable2를 Option-click으로 보면 배열이 아니라 단순 Int로 추론되는데, of operator는 &lt;b&gt;가변적인&lt;/b&gt; 파라미터를 가질 수 있고, swift는 이를 기반으로 자료형을 추론하기 때문이다. 즉, Int를 여러개 전달한 것이지, [Int]를 전달한 것이 아니기 때문에 Int로 추론되는 것이다.&lt;/p&gt;
&lt;p&gt;[Int]자료형으로 observable을 생성하고 싶다면 아래처럼 직접 배열을 전달해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493417209&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let observable3 = Observable.of([one, two, three])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 만들어진 observable3를 Option-click 해보면 [Int]로 추론된 것을 볼 수 있다. of 뿐 아니라 just operator도 배열을 받을 수 있다.&lt;/p&gt;
&lt;p&gt;다음 알아볼 operator는 &lt;b&gt;from&lt;/b&gt;이다. 다음 코드를 아래에 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493427650&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let observable4 = Observable.from([one, two, three])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;from operator는 배열의 각 element에 대해 observable을 생성한다. 그래서 observable4를 Option-click 해보면 [Int]가 아니라 Int인 것을 볼 수 있다. from은 배열만 받을 수 있다.&lt;/p&gt;
&lt;p&gt;아직 아무것도 출력되지 않는 이유는 아직 출력하는 코드를 입력하지도, 실행하지도 않았기 때문이다. &lt;b&gt;subscribing&lt;/b&gt;으로 observable을 구독해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Subscribing to observable&lt;/h2&gt;
&lt;p&gt;iOS 개발을 경험해 봤다면, NotificationCenter(observer들에게 알림을 전달한다.)에 익숙할 것이다. RxSwift의 Observable과 비슷한 것 처럼 보이지만 차이가 있다. 예를 들어, handler closure를 가진 UIKeyboardDidChangeFrame notification을 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611493442920&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let observer = NotificationCenter.default.addObserver(
	forName: UIResponder.keyboardDidChangeFrameNotification,
	object: nil,
	queue: nil) { notification in 
		// handle receiving notification
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RxSwift의 구독도 비슷하다. addObserver() 대신 subscribe()를 사용하게 된다. .default 싱글톤을 사용하는 NotificationCenter와 달리, Rx에서 observable은 모두 개별적인 instance를 가진다.&lt;/p&gt;
&lt;p&gt;더욱 중요한 점은, observable은 subscriber가 없으면 어떠한 이벤트도 호출하지 않고, 어떠한 작업도 하지 않는다는 점이다.&lt;/p&gt;
&lt;p&gt;observable은 sequence의 일종이라는 것을 기억하자. 그리고 observable을 구독하는 것은 Swift standard library Iterator의 next()를 호출하는 것과 비슷하다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493455658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let sequence = 0..&amp;lt;3

var iterator = sequence.makeIteraotr()

while let n = iterator.next() {
	print(n)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;observable을 구독하는 것은 더욱 간소화 되어서 observable이 방출하는 모든 event type에 대해 handler를 추가할 수 있다. observable은 next, error, completed 이렇게 세가지 type의 이벤트를 방출할 수 있는데, next 이벤트는 handler에게 값을 방출하고, error 이벤트는 error instance를 방출한다.&lt;/p&gt;
&lt;p&gt;이것들을 실제로 보기 위해서 예제를 작성해보자. 새로운 예제 코드를 추가하려면 반드시 이전 예제를 마무리하고 독립적으로 작성해야 한다는 것을 잊지말자. 예를들어 아래 예제는 example(of: &quot;just, of, from&quot;)과 독립적으로 작성된 예제코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493467285&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;subscribe&quot;) {
	let one = 1
	let two = 2
	let three = 3

	let observable = Observable.of(one, two, three)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;of operator를 사용한다는 점을 빼면 이전 예제와 비슷한 코드이다. 이제 observable을 구독하기 위해 아래에 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493478607&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observable.subscribe {event in
	print(event)
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note&lt;/b&gt;: 어떤 output이 있을 때 콘솔이 활성화되어야 하지만, 메뉴에서 &lt;b&gt;View &amp;rarr; DebugArea &amp;rarr; ActivateConsole&lt;/b&gt;을 선택하여 수동으로 활성화 할 수 있다. 콘솔은 print의 내용이 출력되는 곳이다.&lt;/blockquote&gt;
&lt;p&gt;subscribe를 Option-click 해보면 Event&amp;lt;Int&amp;gt; 타입을 받고, Void를 반환하는 closure를 파라미터로 받고, Disposable이라는 것을 반환하는 것을 볼 수 있다. disposable에 대해서는 조만간 배우게 될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493507368&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: subscribe ---
next(1)
next(2)
next(3)
completed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;observable은 각 element를 next로 방출한 후, completed 이벤트를 방출하고 종료된다. observable로 작업을 하다보면 결국 이벤트 자체 보다는 함께 방출되는 element을 더욱 중요시 하게 될 것이다.&lt;/p&gt;
&lt;p&gt;방출되는 element를 직접 다루기 위해 subscribe 코드를 아래와 같이 수정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493521080&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observable.subscribe {event in
	if let element = event.element {
		print(element)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event는 element를 가지고 있는데, next 이벤트만이 element를 가지고 있기 때문에 optional이다. element를 unwrap하여 nil이 아닌 경우에만 사용하도록 만들 수 있다. 이제 event와 결합된 형태가 아니라 element를 직접적으로 출력할 수 있게 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493533282&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
2
3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이건 굉장히 자주 사용되는 패턴이다. RxSwift는 이를 좀 더 쉽게 접근할 수 있도록 observable이 방출하는 각 event에 대해 처리할 수 있는 subscribe operator를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493543608&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observable.subscribe(onNext: {element in
	print(element)
})&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note:&lt;/b&gt; 만약 Xcode 자동완성 기능이 켜져 있다면 onNext를 타이핑 했을 때 다른 이벤트도 처리할 수 있는 코드를 볼게 될 수 있지만 지금은 무시해도 괜찮다.&lt;/blockquote&gt;
&lt;p&gt;이제 나머지 이벤트는 무시한 채 next 이벤트에 의해 방출되는 element만 관리할 수 있다. onNext closure는 next 이벤트에 의해 방출되는 element 값을 가지고 있기 때문에 전처럼 event로부터 element를 얻어올 필요가 없다.&lt;/p&gt;
&lt;p&gt;이제 하나의 element를 가지는 observable 부터, 여러 element를 가지는 observable을 생성하는 방법을 알아보았다. 그렇다면 아무 element를 가지지 않는 observable도 존재할 수 있을까? empty operator는 어떤 element도 가지지 않고 completed 이벤트만 방출하는 observable을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493561208&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;empty&quot;) {
	let observable = Observable&amp;lt;Void&amp;gt;.empty()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;observable을 생성할 때 타입 추론이 모호하거나 안되는 상황이라면 반드시 타입을 지정해 주어야 한다. empty는 말 그대로 아무 값을 가지지 않는 observable을 생성하기 때문에 보통 Void 타입을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493571877&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observable.subscribe(
	// 1
	onNext: {element in
		print(element)
	},
	// 2
	onCompleted: {
		print(&quot;completed&quot;)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;next 이벤트를 처리하는 clousure&lt;/li&gt;
&lt;li&gt;completed 이벤트를 처리하는 closure. completed 메세지를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;코드를 실행해 보면 다음과 같은 결과를 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493605944&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: empty ---
completed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그렇다면 empty observable은 언제 사용할 수 있을까? 즉시 종료되거나 값이 0인 observable이 필요할 때 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;empty operator와 반대로, 아무 element도 방출하지 않고 종료되지도 않는 observable을 생성하는 never operator도 있다. 무한한 수명을 가지는 observable이라고 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493617527&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;never&quot;) {
	let observable = Observable&amp;lt;Void&amp;gt;.never()
	
	observable.subscribe(
		onNext: {element in
			print(element)
		},
		onCompleted: {
			print(&quot;Completed&quot;)
		})
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행해 보면 아무것도 출력되지 않는다. 심지어 Completed 메세지도 출력되지 않는다. 그럼 지금 코드가 정상적으로 동작한다는 것을 어떻게 알 수 있을까? 이 의문은 &lt;b&gt;Challenge&lt;/b&gt; 챕터에서 풀릴 것이니 조금만 더 참고 기다려보자.&lt;/p&gt;
&lt;p&gt;지금까지는 특정 element나 값에 대한 observable을 생성했지만 range value에 대해서도 observable을 생성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493627200&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;range&quot;) {
	// 1
	let observable = Observable&amp;lt;Int&amp;gt;.range(start: 1, count: 10)

	observable.subscribe(
		// 2
		onNext: {i in
			let n = Double(i)

			let fibonacci = Int(((pow(1.61803, n) - pow(0.61803, n)) / 2.23606).rounded()
			print(fibonacci)
		})
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;range operator를 사용해서 1부터 10까지 Int 범위를 가지는 observable을 생성했다.&lt;/li&gt;
&lt;li&gt;피보나치 항을 계산해서 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사실 방출된 element를 변경(transform)해서 출력한다는 점에서 onNext보다 더 좋은 방법이 있지만 이 방법은 나중에 배울 챕터인 &quot;Transforming Operator&quot;에서 다루게 된다.&lt;/p&gt;
&lt;p&gt;never() 예제를 제외한 나머지 예제를 다루면서 알 수 있는 사실은 모든 observable은 자동으로 completed 이벤트를 호출해서 종료된다는 점이다. 이러한 예제를 다루면서 osbervable을 생성하고 구독하는 기본적인 기법에 대해 배웠고, 이제 더 나아가기 전에 몇가지 관리 방법에 대해 배울 차례이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Disposing and terminating&lt;/h2&gt;
&lt;p&gt;observable은 구독이 있기 전 까지는 아무 작업도 하지 않는다는 점을 기억하자. subscribe는 observable이 어떤 작업을 수행할 수 있도록 하는 트리거와 같고, error나 completed로 종료되기 전까지 계속 새로운 이벤트를 호출하도록 만든다. 하지만 임의로 구독을 취소하고 observable을 종료할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493643828&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;dispose&quot;) {
	// 1
	let observable = Observable.of(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;)

	// 2
	let subscription = observable.subscribe {event in
		// 3
		print(event)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;String observable을 생성한다.&lt;/li&gt;
&lt;li&gt;observable을 구독한다. 이번에는 반환되는 Disposable의 이름을 subscription이라고 했다.&lt;/li&gt;
&lt;li&gt;방출되는 이벤트를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;구독을 취소하기 위해서 dispose()를 호출한다. 구독을 취소한 후에는 observable이 이벤트를 방출하는 것을 멈출 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493653998&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subscription.dispose()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 구독을 개별적으로 취소하는 것은 지루하고 반복적인 작업이 될 수 있기 때문에 RxSwift는 DisposeBag이라는 type을 제공한다. DisposeBag은 disposed(by:)를 통해 추가된 Disposable들을 가지고 있다가 자기 자신이 소멸할 때 각 Disposable의 dispose()를 한번에 호출해서 구독을 취소한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493665871&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;DisposeBag&quot;) {
	// 1
	let disposeBag: DisposeBag()

	// 2
	Observable.of(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;)
		.subscribe {
			// 3
			print($0)
		}
		// 4
		.disposed(by: disposeBag)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;DisposeBag을 생성한다.&lt;/li&gt;
&lt;li&gt;Observable을 생성한다.&lt;/li&gt;
&lt;li&gt;closure의 기본 파라미터 이름인 $0을 사용해서 event를 출력한다.&lt;/li&gt;
&lt;li&gt;subscribe에 의해 반환된 Disposable을 disposeBag에 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;observable을 생성하고 구독한 뒤 곧바로 dispose bag에 추가하는 이러한 패턴은 아마 RxSwift를 다루면서 가장 많이 사용하게 될 패턴일 것이다.&lt;/p&gt;
&lt;p&gt;disposables 같은 귀찮은 작업을 왜 하는 것일까?&lt;/p&gt;
&lt;p&gt;프로그래머는 항상 실수를 한다. 임의로 구독을 취소하게 하거나 필요한 경우에 dispose를 호출해서 observable을 종료시킬 수 있지만 프로그램이 커지고 이런 작업을 깜빡하게 된다면 메모리 누수가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;Swift 컴파일러는 사용되지 않는 disposable에 대해 warning을 주기 때문에 크게 걱정하지 않아도 된다.&lt;/p&gt;
&lt;p&gt;지금까지 예제에서 우리는 특별한 next 이벤트를 가지는 observable을 생성했다. create operator는 observable이 호출하는 모든 이벤트를 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493681772&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;create&quot;) {
	let disposeBag = DisposeBag()

	Observable&amp;lt;String&amp;gt;.create {observer in
	
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;create operator는 &lt;b&gt;subscribe&lt;/b&gt;라는 하나의 closure만 받는데 이 파라미터는 subscribe를 호출하는 구현을 제공한다. 다시 말해서 이 closure는 구독자(subscriber)들에게 방출되는 모든 이벤트를 직접 호출할 수 있다.&lt;/p&gt;
&lt;p&gt;아직 모든 구현을 작성하지 않았기 때문에 Option-click으로 정보를 보려고 해도 정확한 정보를 얻을 수 없다.&lt;/p&gt;
&lt;p&gt;subscribe 파라미터는 AnyObserver를 받아서 Disposable을 반환하는 escaping closure다. AnyObserver는 subscriber들에게 방출되는 값을 추가하기 용이한 제네릭 타입이다.&lt;/p&gt;
&lt;p&gt;create 코드에 다음과 같은 구현을 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1611493692665&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Observable&amp;lt;String&amp;gt;.create {observer in
	// 1
	observer.onNext(&quot;1&quot;)

	// 2
	observer.onCompleted()

	// 3
	observer.onNext(&quot;?&quot;)

	// 4
	return Disposables.create()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;observer에 next 이벤트를 추가한다. onNext(:)는 on(.next(:))과 같은 의미다.&lt;/li&gt;
&lt;li&gt;observer에 completed 이벤트를 추가한다. 마찬가지로 onCompleted(:)는 on(.completed(:))와 같다.&lt;/li&gt;
&lt;li&gt;또다른 next 이벤트를 추가한다.&lt;/li&gt;
&lt;li&gt;disposable을 반환한다. 예제의 경우 observable이 종료될 때 아무 작업을 안해도 되기 때문에 비어있는 disposable을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Note:&lt;/b&gt; 마지막 Disposable을 반환하는 부분이 이상해 보일 수 있다. subscribe operator는 반드시 disposable을 반환해야 하기 때문에 Disposables.create()로 disposable을 생성해서 반환했다.&lt;/blockquote&gt;
&lt;p&gt;두번째 onNext에서 ?를 방출했던 것을 기억하는가? 이 물음표는 subscriber에게 방출이 될까? 아니라면 왜 방출이 안될까?&lt;/p&gt;
&lt;p&gt;정답을 알아보기 위해 위 create코드 뒤에 subscribe를 추가해서 구독해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611493709227&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.subscribe(
	onNext: { print($0) },
	onError: { print($0) },
	onCompleted: { print(&quot;Completed&quot;) },
	onDisposed: { print(&quot;Disposed&quot;) }
)
.disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;observable을 구독하면서 기본 파라미터 이름을 사용해서 각 이벤트를 출력했다. 예상했던 대로 첫번째 next는 정상적으로 방출이 되어 출력되고, &quot;Completed&quot;와 &quot;Disposed&quot;가 출력된다. 두번째 next는 방출되지 않는데, 이미 completed를 방출하고 observable이 종료되었기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611493718520&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: create ---
1
Completed
Disposed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;error를 추가하면 어떻게 될까? 임의로 새로운 error type을 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611494018354&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum MyError: Error {
	case anError
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 다음, observer.onNext와 observer.onCompleted 사이에 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494038778&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observer.onError(MyError.anError)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 출력되는 내용이 바뀐 것을 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494047901&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: create ---
1
anError
Disposed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;completed나 error도 호출하지 않고, disposeBag에도 추가하지 않는다면 어떻게 될까? 위 코드에서 observer.onError와 observer.onCompleted, disposed(by: disposeBag)을 모두 주석처리하고 실행해보자. 전체 코드는 이렇게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494058235&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;create&quot;) {
	enum MyError: Error {
		case anError
	}

	let disposeBag = DisposeBag()

	Observable&amp;lt;String&amp;gt;.create {observer in
		observer.onNext(&quot;1&quot;)
		// observer.onError(MyError.anError)
		// observer.onCompleted()
		observer.onNext(&quot;2&quot;)
		return Disposables.create()
	}
	.subscribe(
		onNext: { print($0) },
		onError: { print($0) },
		onCompleted: { print(&quot;Completed&quot;) },
		onDisposed: { print(&quot;Disposed&quot;) }
	)
	//.disposed(by: disposeBag)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 observable은 절대 끝나지 않고, disposable은 절대 소멸되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494068043&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: create ---
1
?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 예제를 그대로 두고 앞으로의 예제 코드를 아래에 계속 추가해 나갈 예정이라면 메모리 누수를 막기 위해 onCompleted를 호출하는 부분과 dispose bag에 추가하는 부분의 주석을 해제하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Creating observable factories&lt;/h2&gt;
&lt;p&gt;단순히 subscribe 되길 기다리는 observable을 만드는 대신, 새로운 observable을 생성하는 observable factory를 만들 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494079795&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;deffered&quot;) {
	let disposeBag = DisposeBag()

	// 1
	var flip = false

	// 2
	let factory: Observable&amp;lt;Int&amp;gt; = Observable.deffered {
		// 3
		flip.toggle()
		
		// 4
		if flip {
			return Observable.of(1, 2, 3)
		} else {
			return Observable.of(4, 5, 6)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;어떤 observable을 생성할 지 구분할 수 있도록 플래그를 만든다.&lt;/li&gt;
&lt;li&gt;deffered operator를 사용해서 Int observable을 생성하는 factory를 만든다.&lt;/li&gt;
&lt;li&gt;factory가 subscribe될 때마다 플래그를 변경한다.&lt;/li&gt;
&lt;li&gt;flip 플래그에 따라 다른 observable을 생성해서 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;외부에서 봤을 때 보통 observable과 observable factory는 다른점이 없다. 반복문을 사용해서 factory를 4번 subscribe 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1611494090328&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for _ in 0...3 {
	factory.subscribe(onNext: {
		print($0, terminator: &quot;&quot;)
	})
	.disposed(by: disposeBag)
		print()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;factory를 subscribe할 때 마다, 서로 다른 observable을 얻게 된다. 순서대로 123, 456, 123 ... observable을 얻게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494102259&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: deferred ---
123
456
123
456&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Using Traits&lt;/h2&gt;
&lt;p&gt;Traits으로 필요에 따라 기존 observable보다 더 작은 범위의 observable을 생성할 수 있다. 기존 observable로도 충분히 같은 기능을 구현할 수 있지만 trait의 목적은 사용자 즉, 다른 프로그래머에게 정확한 목적을 전달하는 데 있다. trait을 사용한 코드는 보다 직관적인 가독성을 제공한다.&lt;/p&gt;
&lt;p&gt;RxSwift에는 &lt;b&gt;Single&lt;/b&gt;, &lt;b&gt;Maybe&lt;/b&gt;, &lt;b&gt;Completable&lt;/b&gt; 이렇게 세가지 종류의 trait을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Single&lt;/b&gt;은 success(value) 또는 error를 방출한다. success는 next와 completed의 조합으로, 데이터를 다운받거나 디스크에서 로드하는 것 처럼 한 번에 실행한 후 성공 여부를 방출해야 하는 경우에 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Comletable&lt;/b&gt;은 completed 또는 error만 방출하고 다른 값은 방출하지 않기 때문에 파일 쓰기처럼 완료했는지, 실패했는지 여부만 중요한 작업에 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;마지막으로 &lt;b&gt;Maybe&lt;/b&gt;는 Single과 Completable의 조합으로 success(value), completed, error(error)를 모두 방출할 수 있다. 작업의 성공 여부를 판단하지만 도중에 어떤 값을 방출할 필요가 있는 경우 Maybe를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;앞으로 나올 챕터에서 trait에 대해 더 자세히 배울 수 있다. 하지만 지금은 Resources에 추가한 Copyright.txt 파일에서 텍스트를 읽어오는 예제를 통해 기본적인 사용 방법에 대해 배워 볼 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494115768&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;example(of: &quot;Single&quot;) {
	// 1
	let disposeBag = DisposeBag()

	// 2
	enum FileReadError: Error {
		case fileNotFound, unreadable, encodingFailed
	}

	// 3
	func loadText(from name: String) -&amp;gt; Single&amp;lt;String&amp;gt; {
		// 4
		return Single.create { single in
			
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;dispose bag을 만든다.&lt;/li&gt;
&lt;li&gt;파일을 읽을 때 발생할 수 있는 에러에 대해 enum을 정의한다.&lt;/li&gt;
&lt;li&gt;실제로 파일 읽기를 수행할 Single을 생성하는 함수를 정의한다.&lt;/li&gt;
&lt;li&gt;Single을 생성하고 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;create closure에 다음 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494127432&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
let disposable = Disposable.create()

// 2
guard let path = Bundle.main.path(forResource: name, ofType: &quot;txt&quot;) else {
	single(.error(FileReadError.fileNotFound))
	return disposable
}

// 3
guard let data = FileManager.default.contents(atPath: path) else {
	single(.error(FileReadError.unreadable))
	return disposable
}

// 4
guard let contents = String(data: data, encoding: .utf8) else {
	single(.error(FileReadError.encodingFailed))
	return disposable
}

// 5
single(.success(contents))
return disposable&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;disposable을 생성한다. 각 단계에서 실패하면 closure를 종료해야 하는데 create closure는 disposable을 반환해야 하기 때문에 미리 생성한다.&lt;/li&gt;
&lt;li&gt;파일 이름으로 부터 경로를 생성한다. 실패하면 error를 방출하고 disposable을 반환한다.&lt;/li&gt;
&lt;li&gt;생성한 경로로 데이터를 읽는다. 실패하면 error를 방출하고 disposable을 반환한다.&lt;/li&gt;
&lt;li&gt;읽은 데이터를 string으로 변환한다. 변환에 실패하면 error을 방출하고 disposable을 반환한다. (패턴이 보이기 시작하지 않는가?)&lt;/li&gt;
&lt;li&gt;여기까지 성공했으면 success를 방출하고 disposable을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 함수를 동작하게 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494139320&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
loadText(from: &quot;Copyright&quot;)
// 2
	.subscribe {
		// 3
		switch $0 {
		case .success(let string):
			print(string)
		case .error(let error):
			print(error)
		}
	}
	.disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;loadText에 파일 이름을 전달하면서 호출한다.&lt;/li&gt;
&lt;li&gt;반환된 Single을 구독한다.&lt;/li&gt;
&lt;li&gt;이벤트를 switch로 분기한 뒤, 성공하면 읽은 데이터를, 실패하면 에러를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;성공했다면 콘솔에서 파일의 내용을 볼 수 있을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1611494148509&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--- Example of: Single ---
Copyright (c) 2020 Razeware LCC
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;loadText에 전달하는 파일 이름을 잘못 입력해서 error가 정상적으로 출력되는지도 확인해보자.&lt;/p&gt;</description>
      <category>Programming/RxSwift</category>
      <category>rxswift</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/232</guid>
      <comments>https://eastroot1590.tistory.com/entry/RxSwift-Beginner-Observable%EB%B2%88%EC%97%AD#entry232comment</comments>
      <pubDate>Sun, 24 Jan 2021 22:16:40 +0900</pubDate>
    </item>
    <item>
      <title>독후감 | 수레바퀴 아래서(1906)</title>
      <link>https://eastroot1590.tistory.com/entry/%EB%8F%85%ED%9B%84%EA%B0%90-%EC%88%98%EB%A0%88%EB%B0%94%ED%80%B4-%EC%95%84%EB%9E%98%EC%84%9C1906</link>
      <description>&lt;p&gt;&lt;b&gt;작가&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;헤르만 헤세&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;출판사&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;코너스톤&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;줄거리&lt;/h2&gt;
&lt;p&gt;시골에서 출생한 주인공 한스 기벤라트는 어렸을 때 부터 총명한 머리를 타고났다는 마을사람들의 칭찬에 부합하기 위해 도시로 진학했고 그곳에서 주어진 삶에 적응하지 못하고 방황한다. 결국 실패한 한스는 고향으로 쫓기듯 돌아오고, 여느 마을사람들과 다름없이 기계공으로써의 삶을 사는가 했지만 얼마 지나지않아 사고로 허무하게 죽는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;감상&lt;/h2&gt;
&lt;p&gt;데미안을 읽고 몇개월이 지나서 또 헤르만 헤세의 작품을 골랐다. 중반 정도까지 읽었을 때 청소년기에 동성애를 느끼게 된 주인공에 대한 이야기인 줄 알았는데 주인공이 고향으로 돌아오고 결국 사고로 죽은 결말까지 읽었을 때는 이게 무슨 이야기인지 좀 당황스러웠다. 책 마지막에 있는 해설을 대충 읽어보니 어른들의 무관심이 한스를 죽음으로 내몰았다는 듯한 해설이 있었는데 그 말을 읽고 생각해보면 맞는 말 같다.&lt;/p&gt;
&lt;p&gt;한스는 좋은 머리를 타고난 덕분에 행복한 삶을 살 것 처럼 보였지만 어른들은 어린 한스에게 무책임하고 과분한 부담만 준다. 마을사람들은 물론 중반부 한스가 가장 방황하게 되는 도시학교에서 교장선생님과 다른 선생님들의 무시는 어린 한스가 혼자 이겨내기 힘든 외로움이었을 것이다.&lt;/p&gt;
&lt;p&gt;우리나라에서도 어린 영재나 뛰어난 재능을 가지고 있는 아이들이 미디어에 많이 나왔지만 나이가 들어서까지 행복한 삶을 사는 경우는 드물었던 것 같다. 미디어에 나오지 않았더라도 자신만의 개성을 표현하지 못하고 어른들이 만들어놓은 사회의 틀에서 획일화된 성인으로 자라나는 경우도 많을 것이다. 교육이라는 이 사회과제가 참 오래되었고, 그만큼 어렵다는 생각을 다시 하게 되었다. 한편으로는 이제 나도 서서히 어른이라는 타이틀을 달아야하는 나이가 되어가고 있는데 과연 나는 서툴지 않은 어른이 될 수 있을지 걱정도 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;기억에 남는 장면&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;마지막 한스의 죽음을 묘사한 장면이 영상매체에 자주 등장하는 교차편집처럼 묘사되어 있어서 충격이 배로 다가왔다. 술을 마시고 비틀거리는 한스, 집에서 화를 내는 아버지, 풀숲으로 몸을 던지는 한스, 또다시 집에서 화를 내는 아버지, 그리고 차갑게 강물을 떠내려가는 한스. 글을 읽으면서 장면 전환이 그려져서 좋았다.&lt;/li&gt;
&lt;li&gt;에마와 한스의 애매한 사랑을 묘사한 장면들. 데미안 때도 느꼈지만 이 작가는 어린아이의 감정을 너무 실감나게 잘 묘사하는 것 같다. 사랑이라는 감정이 뭔지도 모르는 한스가 에마를 만나서 사랑에 빠져서 하는 행동들, 에마의 행동들이 굉장히 생생하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Life/독후감</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/231</guid>
      <comments>https://eastroot1590.tistory.com/entry/%EB%8F%85%ED%9B%84%EA%B0%90-%EC%88%98%EB%A0%88%EB%B0%94%ED%80%B4-%EC%95%84%EB%9E%98%EC%84%9C1906#entry231comment</comments>
      <pubDate>Wed, 20 Jan 2021 00:33:45 +0900</pubDate>
    </item>
    <item>
      <title>독후감 | 데미안(1919)</title>
      <link>https://eastroot1590.tistory.com/entry/%EB%8F%85%ED%9B%84%EA%B0%90-%EB%8D%B0%EB%AF%B8%EC%95%881919</link>
      <description>&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;작가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;헤르만 헤세(독일, 1877~1961)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;출판사&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;더스토리&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;줄거리&lt;/h2&gt;
&lt;p&gt;주인공 싱클레어는 어릴적 데미안이라는 신비한 인물을 만난다. 데미안은 또래 아이들과는 다르게 세상을 보는 자신만의 독특한 관점이 있었고, 그의 영향을 받은 싱클레어도 자라면서 서서히 자신만의 세계를 구축해 나가고 하나의 인격으로써 완성된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;감상&lt;/h2&gt;
&lt;p&gt;앨리스와 마찬가지로 제목은 수없이 많이 들어봤지만 정작 읽어보지는 못한 소설이라서 집어보게 되었다. 우연히 최근 TV방송에 나와서 베스트셀러로 새롭게 디자인된 표지로 출판된 책이 있어서 구매했다.&lt;/p&gt;
&lt;p&gt;첫 문장부터 마음을 잡아끄는 무언가 있었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;난 진정 내 안에서 솟아나오려는 것, &lt;br /&gt;그것을 살아보려 했었다 &lt;br /&gt;왜 그것이 그토록 어려웠을까&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 문장은 여러 출판본에서 조금씩 다르게 번역되어서 나왔지만 초판 디자인으로 출판된 책은 위처럼 번역되어있었고, 이 번역이 가장 마음에 들었다.&lt;/p&gt;
&lt;p&gt;거장이라는 말에 걸맞게 초반부부터 모든 문장이 완벽하고, 순수하며, 진심이 느껴지는 수필처럼 느껴졌다. 헤르만 헤세가 자신의 이름을 숨기고 싱클레어라는 가명으로 수필처럼 쓴 소설이라고 하는데 진짜 싱클레어가 자신의 과거를 온전히 털어놓는 것 같은 느낌을 받았다.&lt;/p&gt;
&lt;p&gt;데미안은 소설 속에서 싱클레어가 만나는 친구의 이름인데, 이 데미안이라는 인물로 인해 주인공이 선과 악 두 세계의 모습에 대해 주관적으로 판단할 수 있게되고 자신이 진정으로 원하는 것이 무엇인지 깨닫게 되는 내용이다. 사실 주인공이 성인이 된 이후 데미안으로부터 배우는 내용들은 좀 어려워서 완벽하게 공감하거나 이해하지는 못했지만 유년기 시절 데미안의 이야기로부터는 여러 생각을 할 수 있었다. 특히 내가 선택한 악행을 회계하고 선의 영역으로 들어가려는 행위가 오히려 위선적이고 거짓된 행동일 수 있다는 의견은 꽤 신선한 접근이었다.&lt;/p&gt;
&lt;p&gt;데미안이 말하는 카인의 후예들은 자신이 원하는 것을 명확하게 알고, 자신이 하는 행동이 어떤 세계의 행동인지 명확히 아는 사람들로, 그렇지못한 사람들의 시기와 질투를 사서 악의 상징으로 낙인찍혔다고 한다. 내가 무엇을 원하는지도 모르는체 그저 남들이 옳다고 하는, 선의 세계에서만 살아가려 한다면 알을 깨지 못하고 결국 알 속에서 썩어버릴 것이다. 내가 원하는 것, 타인이 규정한 선과악, 도덕적 경계 등에 구애받지 않고 내가 진정으로 원하는 것이 무엇인지 알아야 한다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;GOOD&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;초반부 싱클레어가 크로머에게 괴롭힘을 당하는 심리에 대한 표현을 너무나 생생하게 표현했다. 기억력이 무한한 어른이 자신의 어렸을 적을 회상하며 기록한 글 같았다.&lt;/li&gt;
&lt;li&gt;글이 그렇게 길지 않아서 좋았다. 딱히 다이제스트를 고른 건 아니라서 원문이라고 생각되는 이 책도 들고다니면서 독서의 입문으로 읽기에 적당했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;BAD&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;흥미진진한 초반부가 지나 싱클레어가 청소년기에 접어들고 성인이 되어가는 과정을 지날 수록 책이 점점 난해하고 어려워진다. 독서라는 행위가 익숙하지 않아서 집중력이 흐려진 것일 수도 있기 때문에 다음에 다시 읽어봐야겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;기억에 남는 장면&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;책의 첫 대사 &quot;난 진정 내 안에서 솟아나오려는 것, 그것을 살아보려 했었다. 왜 그것이 그토록 어려웠을까&quot; 정말 너무나 공감되는 문장이다.&lt;/li&gt;
&lt;li&gt;어린 싱클레어의 심리와 행동을 묘사한 모든 장면. 어린 마음에 했을 법한 행동이 너무나 이해가 되고 그에 따른 어린아이의 고뇌와 괴로움이 정말 생생했다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Life/독후감</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/230</guid>
      <comments>https://eastroot1590.tistory.com/entry/%EB%8F%85%ED%9B%84%EA%B0%90-%EB%8D%B0%EB%AF%B8%EC%95%881919#entry230comment</comments>
      <pubDate>Mon, 11 Jan 2021 01:42:50 +0900</pubDate>
    </item>
    <item>
      <title>iOS Expert | CALayer Tutorial 원형 게이지 만들기 with CAShapeLayer</title>
      <link>https://eastroot1590.tistory.com/entry/iOS-Advenced-CALayer-Tutorial-%EC%9B%90%ED%98%95-%EA%B2%8C%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-with-CAShapeLayer</link>
      <description>&lt;p&gt;UIView 를 다루다 보면 layer라는 인스턴스에 접근하게 될 때가 있다. 이 layer는 CALayer의 인스턴스로 iOS나 OS X가 화면에 컨텐츠를 그릴 때 사용하는 class다. UIView로 내용을 그리는 줄 알았는데, UIView는 이 CALayer를 좀 더 쉽게 다룰 수 있게 하는 일종의 wrapper class였던 것이다.&lt;/p&gt;
&lt;p&gt;그래서 이 CALayer라는 것을 알아보았다. UIView보다 좀 더 로우 레벨에서 CPU에 부담을 주지 않고, 더 자유로운 그래픽 출력을 할 수 있다고 한다. CALayer의 CA는 Core Animation의 약자로 CAAnimation으로 만들 수 있는 property로 구성된 class다. 다시 말해서 CALayer가 가지고 있는 대부분의 property들은 animate시킬 수 있다는 뜻이고, 이들은 모두 &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/AnimatableProperties/AnimatableProperties.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;apple developer library&lt;/a&gt;에 작성되어 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;UIView에서 많이 봤던 이름을 볼 수 있다. 하지만 UIView.animate로 animate시켜보면 duration이 적용이 안되는데, 그 이유는 좀 더 연구해 봐야 겠다.&lt;/blockquote&gt;
&lt;p&gt;간단히 색깔을 바꾸는 animation을 만들어 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1609936493911&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let layer = CALayer()
layer.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
layer.backgroundColor = UIColor.red.cgColor
view.layer.addSublayer(layer)

let animation = CABasicAnimation(keyPath: &quot;backgroundColor&quot;)
animation.fromValue = UIColor.red.cgColor
animation.toValue = UIColor.blue.cgColor
animation.duration = 2
layer.add(animation, forKey: &quot;backgroundColor&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;특이한 점은 key를 단순 문자열로 처리한다는 점인데, enum을 쓰지 않고 단순히 문자열로만 처리하면 오타 때문에 오류가 나기 쉽상인데 왜 이렇게 만들었는지 모르겠다. 심지어 잘못된 property 이름을 입력해도 string 이기 때문에 컴파일 시점에 캐치할 수도 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;display: block; margin: 0 auto;&quot; autoplay=&quot;autoplay&quot; loop=&quot;loop&quot; controls=&quot;controls&quot; height=&quot;700&quot;&gt;&lt;source src=&quot;https://blog.kakaocdn.net/dn/dBa66Z/btqSEMcgtK8/7KkSVmKwpkqQ1hT4TvvgC0/fail.mov?attach=1&amp;amp;knm=tfile.mov&quot; type=&quot;video/mp4&quot; /&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;색이 다시 돌아온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아무튼 이렇게 animate를 시켜 보면 2초동안 파란색으로 바뀌다가 끝나면 다시 빨간색으로 돌아온다. CAAnimation은 duration동안 변화를 시킬 뿐, 실제로 값을 바꾸지는 않기 때문에 toValue로 고정되어야 한다면 animation을 추가하기 전에 최종 색을 정해주어야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1609936656286&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;layer.backgroundColor = UIColor.blue.cgColor
layer.add(animation, forKey: &quot;backgroundColor&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;display: block; margin: 0 auto;&quot; autoplay=&quot;autoplay&quot; loop=&quot;loop&quot; controls=&quot;controls&quot; height=&quot;700&quot;&gt;&lt;source src=&quot;https://blog.kakaocdn.net/dn/wNy9y/btqSIYJ0NbZ/T2PyX2YC1j7i3ojPdLcUyk/success.mov?attach=1&amp;amp;knm=tfile.mov&quot; type=&quot;video/mp4&quot; /&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;최종 값이 유지된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;CAShapeLayer&lt;/h2&gt;
&lt;p&gt;CALayer를 상속받는 여러 Layer들이 제공하는데, CATextLayer의 경우 문자열을 출력할 수 있고, CAEmitterLayer의 경우 파티클을 화면에 출력할 수 있다. 그 중 모양을 출력할 수 있는 CAShapeLayer를 가지고 서서히 차오르는 게이지를 만들어 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;gauge.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot; width=&quot;367&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo78zD/btqSNlj8UEN/vzKp7r4nZdAeArdEWy7nT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo78zD/btqSNlj8UEN/vzKp7r4nZdAeArdEWy7nT1/img.png&quot; data-alt=&quot;동그랗게 차오르는 게이지를 CAShapeLayer로 구현한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo78zD/btqSNlj8UEN/vzKp7r4nZdAeArdEWy7nT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo78zD%2FbtqSNlj8UEN%2FvzKp7r4nZdAeArdEWy7nT1%2Fimg.png&quot; data-filename=&quot;gauge.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot; width=&quot;367&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;figcaption&gt;동그랗게 차오르는 게이지를 CAShapeLayer로 구현한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CAShapeLayer는 CGPath를 입력받아서 화면에 그린다. 따라서 원을 그리려면 원에 해당하는 Path를 만들어서 입력해 주면 된다. 기본 CGPath의 생성자에서 타원을 만들 수는 있지만 여기서 요구하는 원은 아래가 뚫린 원, 정확히는 호 이기 때문에 UIBezierPath로 아래가 뚫린 호를 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1609936781403&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let arc = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat.pi * 3 / 4, endAngle: CGFloat.pi / 4, clockwise: true)
gaugeLayer.path = arc.cgPath&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;path info.png&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;511&quot; width=&quot;717&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cotE0V/btqSNlqUszz/Q8pzicBnN64PY0wvr6QQu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cotE0V/btqSNlqUszz/Q8pzicBnN64PY0wvr6QQu0/img.png&quot; data-alt=&quot;CAShapeLayer는 path정보를 가지고 화면을 그린다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cotE0V/btqSNlqUszz/Q8pzicBnN64PY0wvr6QQu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcotE0V%2FbtqSNlqUszz%2FQ8pzicBnN64PY0wvr6QQu0%2Fimg.png&quot; data-filename=&quot;path info.png&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;511&quot; width=&quot;717&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;figcaption&gt;CAShapeLayer는 path정보를 가지고 화면을 그린다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;게이지를 그리는데 필요한건 stroke이다. 불필요한 fillColor등을 지우고 굵게 표현하기 위해서 lineWidth, lineCap을 설정한다. lineCap의 경우 .butt이랑 .square가 거의 똑같아 보이는데 선 끝 표현 여부에 따라 조금 차이가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;line cap.png&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;598&quot; width=&quot;705&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSC1tb/btqSTJYCFIB/rx7VsmdrBHzkr3ODxkDd80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSC1tb/btqSTJYCFIB/rx7VsmdrBHzkr3ODxkDd80/img.png&quot; data-alt=&quot;butt(왼쪽), square(오른쪽)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSC1tb/btqSTJYCFIB/rx7VsmdrBHzkr3ODxkDd80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSC1tb%2FbtqSTJYCFIB%2Frx7VsmdrBHzkr3ODxkDd80%2Fimg.png&quot; data-filename=&quot;line cap.png&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;598&quot; width=&quot;705&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;figcaption&gt;butt(왼쪽), square(오른쪽)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CAShapeLayer가 Path를 따라 그리는 선을 stroke라고 하는데, path의 시작점과 끝 점에 대응하는 strokeStart와 strokeEnd가 있다. 이 property 또한 animate가 가능하기 때문에 이걸 가지고 점점 차오르는 게이지를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1609937875390&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let gaugeAnimation = CABasicAnimation(keyPath: &quot;strokeEnd&quot;)
gaugeAnimation.fromValue = 0
gaugeAnimation.toValue = toValue
gaugeAnimation.duration = 3 * toValue
gaugeLayer.add(gaugeAnimation, forKay: &quot;strokeEnd&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;strokeStart, strokeEnd는 모두 path에서 상대적인 위치를 나타내기 때문에 시작점은 0, 끝 점은 1이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;display: block; margin: 0 auto;&quot; autoplay=&quot;autoplay&quot; loop=&quot;loop&quot; controls=&quot;controls&quot; height=&quot;700&quot;&gt;&lt;source src=&quot;https://blog.kakaocdn.net/dn/uYOFq/btqSELEmhE4/fOpTMfiyEfmaPm0JuSKF8k/gauge.mov?attach=1&amp;amp;knm=tfile.mov&quot; type=&quot;video/mp4&quot; /&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;strokeColor도 같은 방법으로 animate 시킬 수 있다.&lt;/p&gt;</description>
      <category>Programming/IOS</category>
      <category>ios</category>
      <author>글그리</author>
      <guid isPermaLink="true">https://eastroot1590.tistory.com/229</guid>
      <comments>https://eastroot1590.tistory.com/entry/iOS-Advenced-CALayer-Tutorial-%EC%9B%90%ED%98%95-%EA%B2%8C%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-with-CAShapeLayer#entry229comment</comments>
      <pubDate>Wed, 6 Jan 2021 21:58:46 +0900</pubDate>
    </item>
  </channel>
</rss>