First look at Apple's new Combine framework - Q42 Engineering - Medium
Combine | Apple Developer Documentation
RxSwift와 Combine 비교
- RxSwift에서 새로운 입력 Observables를 만드는 것은 매우 쉽습니다. 예를 들어 다음과 같이 할 수 있습니다.
// Using RxSwift Observables
let observable: Observable<Int> = Observable.create { observer in
// 비동기 아무거나. (예를 들면 네트워크 요청)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
observer.onNext(42)
observer.onCompleted()
})
return Disposables.create()
}
- Combine에서 입력은 Publisher라고 하고 위와 같은 비동기는 이렇게 만들 수 있습니다.
struct MyPublisher : Publisher {
public typealias Failure = Error
public typealias Output = Int
@available(iOS 13.0, *)
public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
_ = subscriber.receive(42)
subscriber.receive(completion: .finished)
})
}
}
- 이 두 스니펫에서 사용 된 인터페이스는 약간 다릅니다.
- 우선 Rx 구독 결과에서 구독을 중지 할 수있는 Disposable이 표시되며 Combine에는 이것이 없습니다.
- 대신, 더 이상 업데이트를받지 않으려면 Publisher 체인의 참조를 취소하면 됩니다.
- 또한 RxSwift의 onNext 반환 유형은 void입니다.
- 스트림을 따라 데이터를 전달할 때 데이터 버블이 백업되지 않습니다.
- Combine을 사용하면
subscriber.receive
를 호출하면 현재 다운 스트림에서 처리 할 수있는 업데이트 수를 나타내는Int
를 반환합니다. - 이를 backpressure*라고하며, RxSwift 또는 RxJS에서는 사용할 수없는 기능입니다.
- backpressure가 있는 RP 라이브러리에서 downstream 처리가 가능한 데이터가 더 많으면 backpressure가 '적용'됩니다.
- backpressure를 가지거나 가지지 않거나는 설계 상의 선택이고 중요한 것은 Combine은 이것을 가지고 있다는 것입니다.
backpressure*
- 역압, 배압(背壓)(기관의 배기측(排氣側)의 가스압(壓) 따위가 정상적인 압력 방향과 반대임)
Publishers
- Publisher는 프로토콜이므로 그것을 구현하는 모든 구조체를 만들 수 있지만, Apple은 또한 Publishers (복수형) 열거 형에 사전 제작 된 Publishers 세트를 제공합니다.
- 위 예제에서 우리는 구조체 방식의 비동기 Publisher를 만들었습니다. 그 대안은
@available(iOS 13.0, *)
let p = Publishers.Future { (subscriber: @escaping (Result<Int,Error>) -> Void) in
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
subscriber(.success(42))
})
}
- 우리는 작업을 수행하는 함수 블록을 제공했습니다.
- 원하는 지점에 subscriber closure를 붙이기만 하면, 이 함수 내에서 원하는 모든 것을 할 수 있습니다.
- 다른 유용한 Publisher들
@available(iOS 13.0, *)
let p1 = Publishers.Empty<Any, Error>()
@available(iOS 13.0, *)
let p2 = Publishers.Just(42)
@available(iOS 13.0, *)
let p3 = Publishers.Sequence<[Int], Error>(sequence: [1,2,3,4])
Operators
- Rx에서 알 수있는 여러 연산자가 있습니다. 예를 들어.
func run () {
if #available(iOS 13.0, *) {
// Create
let p1 = Publishers.Empty<Int, Error>()
let p2 = Publishers.Just(42)
let p3 = Publishers.Sequence<[Int], Error>(sequence: [1,2,3,4])
// Operators
let p4 = p1
.merge(with: p2)
.append(5) // add 5 to the end of the sequence
.allSatisfy { $0 >= 1 } // check if all values are bigger than 0
.count() // how many values: 1
}
}
- 아직 전체 목록을 살펴 보진 않았지만 Rx 연산자의 대부분이 표현 된 것처럼 보입니다.
- 그밖에는 스스로 구현하는 것이 아주 간단할 것입니다.
Subscribers
- 스트림에서 값을 가져 오는 것은 Subscribers와 함께 작동합니다. 예를 들어
.sink
let p3 = Publishers.Just(42)
p3.sink { (value) in
debugPrint(value)
}
- 쉽습니다. 그러나 Combine의 힘에 대한 더 놀라운 예가 Subscribers.Assign subscriber입니다.
- 예를 들어 다음에서 참조 유형 article의 제목 필드를 어떻게 동적으로 할당 할 수 있는지 알아보십시오.
@available(iOS 13.0, *)
func exampleCombineKVO () {
let article = Article(title: "Test", body: "Lorum ipsum")
// KeyPath 101의 경우, https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift
let keypath: ReferenceWritableKeyPath<Article,String> = \.title
// 새로운 Combine 개체
let sink = Subscribers.Assign(object: article, keyPath: keypath)
let source = Publishers.Future<String, Never> { subscriber in
DispatchQueue.main.async {
subscriber(.success("42"))
}
}
source.subscribe(sink)
}
- 정말 멋진 기능입니다!
RxSwift와 Combine 간의 상호 운용성
- 물론 두 리액티브 프레임워크는 함께 쓸 수 있다.
- Combine으로 전체 코드베이스를 변환 할 필요가 없이, Observable을 SwiftUI에 넣는 것이 가능합니다.
- 이렇게 하면 Observable에서 Publisher로 변환할 수 있다.
extension Observable : Publisher {
@available(iOS 13.0, *)
public func receive<S>(subscriber: S) where S : Subscriber, Observable.Failure == S.Failure, Observable.Output == S.Input {
_ = self.subscribe(onNext: { (next) in
let demand = subscriber.receive(next)
}, onError: { (err) in
subscriber.receive(completion: .failure(err))
}, onCompleted: {
subscriber.receive(completion: .finished)
})
}
public typealias Failure = Error
public typealias Output = Element
}
- 반대도 가능해야 한다.
demand
속성은 사용되지 않는다는 점에 유의하십시오.- RxSwift는 back-pressure가 없으므로 전환 할 때 이미 조절 한 Observables 만 사용하고 UI 시스템을 오버런하지 않는지 확인해야합니다. ❓
- 또한 떨어뜨리거나 버퍼링하여 back-pressure을 하는 일종의 동적 operator를 만들 수도 있을 것이다. ❓
마무리
- 흥미롭게도 Combine은 Foundation 타입에 의존하지 않는다.
- 사실, 그것은 Foundation보다 낮은 레벨에 있다.
- 애플 엔지니어의 말을 인용
“RxSwift와 Combine의 메모리 모델은 매우 다르다. Combine은 정말 성능을 위해 만들어졌다.“
-
기존의 Publisher를 보면, 애플은 이미 RealityKit 프레임워크와 같은 고성능 시스템에서 이것을 사용하고 있다.
-
예를 들어
Scene.Publisher
-
RxSwift와 비교했을 때, Combine은 매우 다른 구조(semantic)를 가지고 있다.
-
DisposeBags의 부재와 back-pressure의 존재는 아마도 Apple이 성능 및 통합에 미치는 직접적인 영향은 다른 낮은 수준의 시각화 및 AR 시스템에서 두 배로 감소함. ❓
-
프레임률 종속 시스템 사용: 역압 사용, RP 시스템은 실제로 Rx와 같은 완전한 반응형 프레임워크보다는 장기 폴링/샘플링 시스템에 가깝다. ❓
-
이 첫 번째 베타 릴리즈에서, 애플이 제공하는 Publisher들과 Sink 세트는 여전히 작다. ❓
-
하지만 한 소식통에 의하면 다가올 릴리즈에는 URLSession, NotificationCenter & KVO와 같은 일반적인 Observable 항목을 위한 더욱 편리한 Publisher가 생길 것이라고 합니다.
댓글