저번 달부터 회사 동기들이랑 컴바인 스터디를 하고 있다.
나도 정리도 나름 열심히 해가고 도움도 많이 되고 있어서 블로그에도 기록용으로 남겨본다✌🏻
비동기 이벤트를 처리하는 애플의 프레임워크.
시간이 지남에 따라 값을 처리하기 위한 선언형 API들을 제공한다.
Publisher: 시간에 따라 변할 수 있는 값을 노출
Subscriber: Publisher로부터 값을 받고 그에 대한 액션을 취함
여러 publisher들의 값들을 결합하고, 상호작용을 조절할 수 있음
protocol Publisher<Output, Failure>
하나 이상의 Subscriber에게 값을 전달한다.
Subscriber의 Input, Failure 타입과 Publisher의 Output, Failure 타입이 일치해야 함
receive(_:)
메소드를 호출한 다음에 subscriber의 receive
메소드를 호출할 수 있음.
얘네를 사용해서 subscriber와 커뮤니케이션(?) 할 수 있다.
receive
메소드 종류는 3가지!
→ 위의 세 메소드들은 Subscriber 프로토콜 안에 있는 메소드들임.
Publisher는 프로토콜이기 때문에 원래는 직접 구현해야 한다. 하지만 Combine에서는 다양한 Publisher 타입(?)을 제공한다.
send()
메소드 호출해서 실시간으로 값을 publish 할 수 있다@Published
어노테이션 붙이기Publisher..? Publishers..?? 넌 뭐냐?? 🤯
Publishers
를 상속(??)하는 클래스나 구조체로 기능을 구현한다.Publishers.Contains
인스턴스를 반환한다.["Pepperoni", "Mushrooms", "Onions", "Sausage", "Bacon", "Extra cheese", "Black olives", "Green peppers"].publisher
나는 값을 여러 번 방출하고 싶어!! → publisher로 손쉽게 변환할 수 있다.
여러 개의 값을 방출하고 종료함.
.publisher
라는 키워드를 붙여서 publisher를 만들 수 있다
// create the order
let pizzaOrder = Order()
let pizzaOrderPublisher = NotificationCenter
.default
.publisher(for: .didUpdateOrderStatus, object: pizzaOrder)
// once the user is ready to place the order
NotificationCenter
.default
.post(name: .didUpdateOrderStatus,
object: pizzaOrderPublisher,
userInfo: ["status": OrderStatus.processing])
❓ 궁금한 점..
notification center를 반드시 사용해야 하는가?
어떤 경우에서 사용하는 건가? 라고 생각했었는데
애플 공식문서에 있던 예시는 macOS를 개발하는 AppKit에서 사용하는 방법을 알려주는 예시였다.
나중에 Swift: NotificationCenter와 사용법 를 읽어봐도 좋을 듯!
protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible
subscribe(_:)
메소드 호출해서 subscriber와 publisher를 연결할 수 있다.Publisher의 subscribe(_:)
메소드 호출
func subscribe<S>(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
Apple 왈,, receive(subscriber:)
대신 얘를 호출하라고 함. 이 함수 안에서 receive(subscriber:)
를 호출할 거임.
그러면 publisher가 subscriber의 receive(subscription:)
메소드를 호출하게 됨
subscriber에게 Subscription
인스턴스를 전달해줌
subscriber가 처음으로 Demand를 생성함
publisher가 subscriber의 receive(_:)
메소드를 호출해서 새로 publish된 요소를 전달함.
publisher가 publish를 중단하면 receive(completion:)
메소드를 호출함
Subscriber.Completion
타입의 파라미터 사용🤯 아니.. receive 메소드가 publisher꺼야 subcriber꺼야;; 겁나 헷갈리네
1️⃣ receive(subscriber:)
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
publisher에게 subscriber를 붙이는 메소드
직접 이 메소드를 호출하지 않고 subscribe(_:)
를 호출하면 얘를 호출해준다.
값을 받는 것 / 생애주기 이벤트를 받는 것으로 나뉨
1️⃣ receive(_:)
func receive(_ input: Self.Input) -> Subscribers.Demand
subscriber에게 publisher가 항목을 만들어냈음을 알려주는 메소드
input
: publish된 요소Subscribers.Demand
: subscriber가 얼마나 많은 요소들을 더 받을 것으로 예상하는지를 나타내는 인스턴스2️⃣ receive()
func receive() -> Subscribers.Demand
void 요소의 publisher가 다음 요청을 받을 준비가 되었음을 알려주는 메소드
이벤트가 발생했다는 것만 알려주고 싶을 때 Void
input / output을 사용
3️⃣ receive(subscription:)
func receive(subscription: any Subscription)
subscriber에게 publisher를 성공적으로 구독했고, 항목을 요청할 수 있음을 알려주는 메소드
4️⃣ receive(completion:)
func receive(completion: Subscribers.Completion<Self.Failure>)
subscriber에게 publisher의 퍼블리싱이 끝났음을 알려주는 메소드
Subscriber.Completion
: 퍼블리싱이 정상적으로 끝났는지 or 에러를 뱉었는지 알려줌Combine은 Publisher 타입에 있는 operator 형태의 subscriber를 제공한다.
enum Subscribers
Publisher와 마찬가지로 subscriber 역할을 하는 타입들을 모아둔 네임스페이스가 있다.
max(_:)
unlimited
none
publisher가 정상적인 종료 or 에러 발생에 의해 더이상 값을 만들어내지 않음을 알려주는 신호
finished
failure(Failure)
struct Just<Output>
Never
로 정의되어 있기 때문Never 는 코드에서 실행되면 안되는 부분을 명시적으로 나타냅니다.
Never 를 이용해서 코드를 볼 때 ‘아 이부분은 실행이 되면 안되는 부분이구나’ 라고 판단할 수 있을 것이다. 앱을 강제 종료 해야 한다거나, UIEvent 에 Error 를 써야 할 때 활용할 수 있을 것 같다. 고 한다..
Never keyword in Swift: return type explained with code examples
Publisher가 에러를 뱉지 않으면 Never
를 사용함
final class Future<Output, Failure> where Failure : Error
하나의 값을 만들고 결과를 보내는 (종료 or 실패) publisher
Future.Promise
typealias Promise = (Result<Output, Failure>) -> Void
Result<Output, Failure>
: Future가 publish한 값 or 에러public init(_ attemptToFulfill: @escaping (@escaping (Result<Output, Failure>) -> Void) -> Void)
사실상 이렇게 정의되어 있는 것
func generateAsyncRandomNumberFromFuture() -> Future <Int, Never> {
return Future() { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let number = Int.random(in: 1...10)
promise(Result.success(number))
}
}
}
Future는 Swift Concurrency로 대체 가능하다고 하는데,, 나중에 알아보는 걸로~
Defer: 미루다, 연기하다
struct Deferred<DeferredPublisher> where DeferredPublisher : Publisher
새로운 Subscriber의 Publisher를 만들기 위해 클로저를 실행하기 전 Subscription을 기다리는 Publisher
createPublisher
는 subscribe(_:)
가 호출될 때 실행된다. 이름 그대로 나중에 호출될 Publisher를 새로 만드는 클로저let createPublisher: () -> DeferredPublisher
createPublisher
에 의해 리턴된 publisher는 subscription을 바로 받는다.
struct Empty<Output, Failure> where Failure : Error
아무 값도 publish 하지 않고, 즉시 종료될 수 있는 publisher
Empty(completeImmediately: false)
: 어떤 값도 보내지 않고, 결과(종료 or 실패)도 보내지 않는 “Never” publishercompleteImmediately
: Publisher가 바로 completion을 보낼지 여부struct Fail<Output, Failure> where Failure : Error
특정한 에러와 함께 바로 종료되는 publisher
struct Record<Output, Failure> where Failure : Error
여러 개의 input들과 하나의 completion을 기록하고, 나중에 subscriber에게 전달해주는 publisher
recording
을 가지고도 새로 생성 가능Record
가 기록한 결과를 보관해두는 역할을 하는 구조체
receive(_:)
, receive(completion:)
로 각각 새 output과 completion을 저장함@frozen
struct AnyPublisher<Output, Failure> where Failure : Error
다른 publisher를 감싸서 타입을 지우는 publisher
언제 사용하지?
Subject
의 send(_:)
메소드가 호출되는 걸 막고 싶을 때 → 값 변경을 막음eraseToAnyPublisher()
를 사용해서 publisher를 AnyPublisher로 감쌀 수 있다
final class Sink<Input, Failure> where Failure : Error
무한정으로 값들을 요청하는 subscriber
final class Assign<Root, Input>
받은 항목을 key path를 사용해서 프로퍼티에 할당하는 subscriber
Never
이다.object
의 프로퍼티에 값을 할당한다.keyPath
를 사용해서 나타낸다. (key-path expression은 여기를 참고하자..)class SampleObject {
var intValue: Int {
didSet {
print("intValue Changed: \(intValue)")
}
}
init(intValue: Int) {
self.intValue = intValue
}
deinit {
print("sample object deinit")
}
}
let myObject = SampleObject(intValue: 5)
let assign = Subscribers.Assign<SampleObject, Int>(object: myObject, keyPath: \.intValue)
let intArrayPublisher = [6,7,8,9].publisher
intArrayPublisher.subscribe(assign)
@frozen
struct AnySubscriber<Input, Failure> where Failure : Error
Subscriber
를 직접 구현하지 않고, 프로토콜 안에 정의된 메소드를 위한 클로저를 사용해서 커스텀 subscriber를 만들고 싶을 때도 사용한다고 한다.. → 많이 사용할 일은 없지 않을까?Publisher
프로토콜을 채택한 구조체 (Future 제외)였음Subscribers
안에 정의되어 있고, class 타입이다.protocol Cancellable
어떤 작업에 대한 취소가 가능함을 나타내는 프로토콜
cancel()
store(in:)
Subscription
프로토콜이 Cancellable
을 채택
→ thread-safe 하기 위해
final class AnyCancellable
취소되었을 때 클로저를 실행하는 type-erasing cancellable 객체
cancel()
을 호출한다.