본문 바로가기
iOS/Combine

#1 Hello, Combine

by HaningYa 2020. 10. 27.
728x90

[출처:www.raywenderlich.com/books/combine-asynchronous-programming-with-swift/v2.0]

목차

  • 비동기 프로그래밍 이란
  • Combine의 원천
  • Combine 기본
  • Combine의 장점
  • 앱 아키텍쳐
  • 샘플 프로젝트
  • 요약

Declarative 하고 reactive 한 앱을 위한 Combine을 배워보자

애플의 말에 따르면

"The Combine framework provides a declarative approach for how your app processes events. Rather than potentially implementing multiple delegate callbacks or completion handler closures, you can create a single processing chain for a given event source. Each part of the chain is a Combine operator that performs a distinct action on the elements received from the previous step"

  • app 이 event 를 처리할 때 declarative한 접근을 제공한다. (여러개의 delegate, callback, completion handler 사용X)
  • 주어진 event source 에 대해 single processing chain 을 만들 수 있다.
  • 각각의 chain 은 Combine operator 이고 이전 step 에서 받은 element 에 대해 특정 action을 수행한다.

코드를 직접적으로 보기전 combine 이 해결할 수 있는 문제들과 사용하는 tool에 대해서 배워 볼 것 이다.

그리고 관련된 용어들과 framework 에 대한 전반적인 이해도를 높일 것 이다.

점차적으로 책을 공부하며 심화된 주제에 대해 배워 보고 몇가지 프로젝트를 통해 알아갈 것 이다.

Asynchronous programming

single threaded 언어에는 코드는 순차적으로 한줄씩 실행된다.

그래서 synchronous code 는 이해하기 쉽고 데이터의 상태에 대해 얘기하기 편하다.

만약 multi-threaded 언어로 asynchronous event drivien ui framework 일때 를 생각해보라

각각의 코드는 동시에 실행될 수 있고 누가 먼저 실행될지 모르기 때문에 데이터가 어떤 상황인지 모른다.
(실행 할 때 마다 다른 결과가 나옴)

시스템이 어떤 코드를 먼저 load 하는 지에 따라 결과가 다르다.

이러한 state 를 관리해야하는게 까다로운 작업이 된다.

Foundation and UIKit/AppKit

Apple 은 비동기 프로그래밍을 계속 개발해 왔다.

여러 시스템 레벨에 걸쳐서 메커니즘이 적용되 있기 때문에 개발자는 앱을 개발할때 비동기적 관점에서 따로 신경쓰지 않고 앱을 개발해왔다.

아마 밑에 것들을 써봤을 것 이다.

  • NotificationCenter : 어떤 이벤트가 발생할 때 실행되는 코드조각 (device orientation 이나 sofrware keyboard)
  • Delegate pattern : 다른 객체에 연관되어 동작하는 객체 (remote notification이 언제 올진 모르지만 왔을 때 무슨 행동을 해야될지 정의는 해둠)
  • GCD, Operation : 작업을 추상적으로 실행하게 도와줌. serial queue를 통해 순차적 실행 또는 priorities 를 통해 각각의 queue에 대해 병렬적으로 실행시킬 수 있음
  • Closures : detached 코드 조각을 만들어 코드에 전달해서 다른 객체가 그것을 실행시킬지 말지를 결정(얼마나, 언제 실행할지)

UI 이벤트나 몇몇의 작업들은 비동기적으로 동작하기 때문에 전체적인 실행 순서를 알 수 없다.

하지만 비동기 프로그램을 작성하는 것은 가능하다. 단지 더 복잡할 뿐이다.

비동기 코드와 리소스 공유는 재현하기 힘든 에러들을 만들 수 있다. (재현해서 고치는게 힘들어 진다.)

까놓고 말하면 지금 있는 앱들은 대부분 다른 비동기 API 를 사용한다. (각각 인터페이스가 따로 있는)

하나의 데이터에 대해 아래의 API 들이 각자 다른 interface를 가지고 접근한다.

  • NotificationCenter
  • Delegates
  • Closure callbacks
  • GCD
  • Operations
  • Timers

Combine은 Swift 생태계에서 비동기 지옥에 순서를 되찾아 주러 온 새로운 언어를 소개한다.

Apple 은 Combine API 를 Foundation framework 속에 통합해 놔서 Timer, NotificationCenter, Core Data 등은 이미 Combine 과 같이 동작한다. 또한 Combine은 개발자 코드에게도 쉽게 통합이 가능하다.

마지막으로 SwiftUI + Combine 도 좋은 조합이다.

아래는 Apple 이 combine 을 사용한 reactive programming 에 얼마나 노력했는지 보여주는 자료이다.
Combine은 system 계층에 있는데

  • SwiftUI
    • @State
    • @Binding
    • @ObservedObject
  • Foundation
    • Timer
    • NotificationCenter
  • CoreData
    • FetchedRequest
    • NSManagedObject
  • Combine

여러 시스템 프래임워크는 Combine 에 의존하여 Combine은 전통적인 API 에 대안을 제시한다.

Combine 은 Apple framework 이기 때문에 Timer, NotificationCenter와 같은 양질의 API를 대체하기 위해 만들어진 것은 아니다.

이러한 Foundation type들은 계속 자신의 역할을 하고 대신 Combine은 그것들과 통합되어 새롭게 비동기적으로 앱에서 동작할 수 있는 universal langueage 역할을 하게 된다.

앱의 모든 부분, 데이터 모델부터 네트워킹 레이어와 UI 까지 하나의 비동기 tool로 만들수 있으니 좋지 아니한가.

Foundation of Combine

declaritive, reative programming 은 새로운 컨셉은 아니지만 요 몇년 사이 단점들이 많았다.

현대 적인 reactive solution은 MS 에서 .NET framework 에 도입한 Reactive Extension 라이브러리 부터 시작됬다.

MS 는 Rx.Net을 만들어 오픈소스로 뿌렸고, 그 이후 많은 다른 언어들이 이 컨셉을 사용하기 시작해서 RxJS, RxKotiln, RxScala, RxPHP 등의 Rx 표준을 따르는 것들이 생겼다.

Apple platform 에서 RxSwift와 같은 몇몇의 서드파티 reactive framework가 있다. 

Combine은 Rx와 비슷하지만 다른 Reactive Stream 이라는 개념을 제시한다.

Reactive Stream 은 Rx와 몇가지 다른 점이 있지만 핵심 개념은 동일하다.

iOS13, Catalina에서 Combine framework를 지원한다.

Apple에서 나온 새로운 기술이라 몇가지 한계점도 존재한다. (min target)

하지만 Apple 이 밀고있기 때문에 금방 널리 퍼질것으로 생각된다.

Combine basics

publisher, operators, subscribers가 Combine 을 움직이는 3대 요소이다.

다른 선수들도 있지만 이 세개 없이는 거의 아무것도 못한다.

챕터 2에서 Publisher, subscribers를 배워볼 것이다. 

Publishers

데이터에 대해 흥미를 가지는 여러 애들(subscriber) 한테 값을 emit 할 수 있는 타입이다.

publisher 의 내부로직이 어떻든 모든 publisher 는 아래의 3개 의 event를 emit 할 수 있다.

  1. output value of publishers generic Output type
  2. successful completion
  3. with error of publishers' failure type

Publisher는 0 이상의 출력값을 emit 할 수 있고 성공해서 작업 완료하거나 실패했을 경우 추가로 event 를 emit 하지 않는다.

https://miro.medium.com/max/1200/1*CLMoTLQAjhFaNFWgZAUrKw.png

선은 timeline을 나타내고 파란 원은 시간에 따라 emit 되는걸 나타내고 원 안의 숫자는 emit 되는 값을 나타낸다.

오른쪽의 수직 선은 stream 이 성공한 completion을 나타낸다.

이러한 3개의 표현은 모든 가능한 event를 표현할 수 있게한다. 이게 왜 앱에서 계산을 하던 network 호출하던 사용자 제스쳐에 반응하던 화면에 정보를 표시하던, 모든 작업을 combine publisher 로 할 수 있게 해주는 이유이다. 

delegate 를 사용하던 injecting completion callback을 사용하던 고민할 필요없이 publisher 를 사용하면 된다.

error handling 이 기본 지원되는 장점이 있는데 필수로 넣어야 하는건 아니다.

Publisherprotocol 은 두개의 타입에 대해 generic 한데 이전에서 봤듯이

  • Publisher.Output : publisher 가 배출한 값. Publisher 가 Int로 특화되있으면 String이나 Date 같은 다른 값 emit 불가
  • Publisher.Failure : 실패할 수 있는 코드에 대한 에러 핸들링. 만약 절대 실패할 수 없을 경우 failure type 에 Never 명시

publisher를 subscribe 할경우 어떤 타입의 값이 넘어오는지, 실패 했을 경우 어떤 error를 처리할 건지 알고 있어야 한다.

Operations

operator 는 Publisher 프로토콜에 선언된 메소드로써 같거나 새로운 publisher 를 리턴한다.

여러개의 operator가 연결되어 있을 때 진가를 발휘한다.

이러한 operators는 고도로 decoupled(분리된), composable(구성가능한) 되어 있어 여러개를 합쳐서 복잡한 로직을 하나의 subsciption 의 실행에 붙여넣을 수 있다.

operator들은 퍼즐처럼 맞춰질 수 있고 이전 operator 의 output 과 다음 Operator 의 input 타입이 맞아야 하므로 절대 실수로 순서를 바꿀 수 도 없다.

단순명로한 방법으로 각각의 추상적인 작업이 비동기적으로 순서대로 처리되서 내장 에러 핸들링도 지원하니 좋지 아니한가

보너스로 operator들은 Input과 output 이 있는데 보통 upstream 과 downstream으로 불린다.

이렇게 shared state 를 피할 수 있다.

operator들은 이전 operator 가 준 데이터를 처리하는데 집중하고 output 을 chain 의 다음 operator 에게 전달한다.

이 말은 operator 실행 도중 값이 다른 비동기 코드로 점프해서 작업중인 데이터를 바꿔버리지 못한다는 뜻이다.

Subscribers

마지막으로 subscription chain의 마지막인 subscriber 이다.

모든 subscription은 subscriber 에서 끝난다.

Subscriber 들은 보통 emitted output 이나 completion event를 가지고 어떤 필요한 작업들을 한다.

현재 Combine 2개의 기본 subscriber 를 제공한다.

  • sink subscriber : output value 와 completion 에서 받을 closure 코드를 제공할 수 있도록 해준다. 받은 이벤트를 가지고 원하는데로 처리할 수 있다.
  • assign subscriber : custom code 없이 resulting output 을 다른 data model 에 있는 프로퍼티 또는 UI control(key-path사용)로 bind 해준다.

custom subscribers를 만드는 것은 publisher 를 만드는거 보다 쉽다.

Combine은 간단한 protocol 집합을 사용해서 task 에 필요한 custom tool 을 만들 수 있도록 제공한다.

Subscriptions

* 이 책은 subscription 이라는 용어를 Combine Subscription protocol 과 그것을 준수하는 객체, completion chain of publisher, operators, subscriber 들을 의미할 때 사용한다.

subscriber를 subscription 끝에 달아주게 되면 해당 chain에 있는 모든 publisher 를 활성화 시킨다.

publisher 는 만약 결과값을 받을 subscriber 가 없다면 절대 value 를 emit 하지 않는다.

Subscription들은 비동기 이벤트들을 묶은 chain을 한번만 선언하게 하고 그 뒤론 생각 안해도 되는 마법같은 컨셉이다.

앱 전체를 combine 으로 구현하면 app logic은 subscription으로 설명할 수 있고 system 이 다른 객체에 대해 데이터를 가지고 오거나 보내고 콜백을 받는 등의 생각은 할 필요가 없어진다.

subscription code 가 컴파일 되고 따로 로직 문제가 없으면 앱은 비동기적으로 각각의 이벤트(제스쳐 타이머)등을 발사할 것 이고 다른 Publisher들에게 전달할 것 이다.

더욱 좋은건 Combine 의 Cancellable 이라는 프로토콜 덕분에 subscription은 기억할 필요가 없다. (dispose 느낌)

두개의 시스템이 제공하는 subscriber(sink, assing) 은 Cancellable 프로토콜을 준수해서 subscription code 가 Cancellable object를 리턴한다. 객체를 memory에서 해제할 경우 subscription 통체로 취소하고 리소스를 메모리로 부터 해제한다.

이 의미는 subscription의 생애주기를 view controller 의 프로퍼티로 저장해 bind 할 수 있다는 것 인데, 예를들어 user 가 view conroller를 dismiss 할 때 subscrption 도 같이 cancel 될 수 있는 것 이다. 

이 프로세스를 자동하 하려면 [AnyCancellable] Collection propery 를 타입으로 가지고 있어 원하는 만큼 subscription을 넣어두면 된다. 그럼 자동으로 canceled 되고 메모리로 부터 release 될 것이다. (disposableBag 느낌)

What's the benefit of Combine code over "standard code

  • combine은 비동기 코드에 다른 추상화를 더하도록 목표되었다.
  • combine은 시스템 레벨에서의 추상화 계층이기 때문에 시스템과의 tighter integration을 뜻하고 그것은 많이 테스트 되고 오랫동안 지원될 안전한 기술이라는 의미이다. (하다가 버릴 기술이 아님)
  • combine 을 사용할지 말지는 내생각이지만 몇가지 pro 입장의 이유가 있다.
    • combine 은 시스템 레벨에서 통합되어 있다. 이뜻은 language가 open 된 기능도 쓰고 있다는 것이다. (본인이 만들수 없는 API 를 제공하는 것이다.)
    • delegate, IBAction, closure 를 통한 옛날 방식의 비동기 코드는 각각의 제스쳐에 따라 custom code를 작성해야 되는 부담이 있다. 그 뜻은 테스트할 custom code들도 많아진 다는 것이다. Combine은 비동기 operation들을 operators로 추상화한다. (이미 테스트가 끝난 상태)
    • 모든 비동기 코드가 같은 interface (publisher,)를 사용할 때 compostion(구성) 과 재사용성이 강력히 증가된다.
    • combine operator 는 여러 조합이 가능해서 새로운 operator 를 만들면 plug-and-play, 그냥 갇다 꽂으면 Combine 이 알아서 해준다.
    • 비동기 코드를 테스팅 하는 동기 코드를 테스팅하는 것 보다 어렵지만 Combine 은 이미 비동기 operator 들이 테스트 된 상태이기 때문에 busniss logic 만 test 하면 된다.

Apple 이 밀고있으니 배우는게 좋을꺼다

App architecture

combine 이 기존 앱 구조를 어떻게 바꿀지 생각해 본다.

  • combine은 앱 구조에 영향을 주는 framework 는 아니다.
  • combine 은 비동기 적인 데이터 이벤트와 통일화된 communication contract 를 다룬다.
    (프로젝트에서 어떤 파트가 어떤 책임을 지는지는 바꾸지 않는다.)
  • Combine을 MVC, MVVM, VIPER 다 사용이 가능하다.
  • 이 뜻은 codebase를 향상시키기 위해 Combine 을 선택적으로 도입할 수 있다는 뜻이다. 
  • 데이터 모델부터 바꾸는 것으로 시작해 네트워크 레이어에도 적용시킬 수 있고 새로운 코드 작성때만 combine 을 도입할 수 도 있다.
  • SwiftUI를 도입하는 것과는 조금 다르다. (SwiftUI 는 MVC 에서 C를 없앤다.)
  • combine/swiftui 를 잘 도입하면 view controller는 설자릴 잃게된다.

15장에서 swiftui/combine을 같이 써보는 연습을 할 것 이다.

Key points

  • combine은 declaritive, reactive framework 로 비동기 이벤트를 처리한다.
  • 현존하는 문제를 해결하기 위해 나왔으며 비동그 프로그래밍 툴을 하나로 합친 느낌이다.
  • 3개의 메인 플레이어가 있는데
    • publisher: emit event 
    • operator: asynchronously process & maipulate upstream event
    • subscriber : consumer of result

 

728x90

'iOS > Combine' 카테고리의 다른 글

#12 Key-Value Observing  (0) 2020.10.30
#9 Networking  (0) 2020.10.28
"UI events are asynchronous"  (0) 2020.10.28
#3 Transforming Operators  (1) 2020.10.28
#2 Publishers & Subscribers  (0) 2020.10.27

댓글