Publisher
Declares that a type can transmit a sequence of values over time.
형식이 시간이 지남에 따라 값 시퀀스를 전송할 수 있음을 선언합니다.
Publisher
는 하나 이상의 Subscriber
인스턴스에게 Element
를 전달한다.
쉬운 말로 값을 방출하는 녀셕!!!
또한 Publisher
는 프로토콜이다.
내부엔 Output
, Failure
, receive(subscriber:)
를 필수로 구현해야 한다.
하나씩 보면,
Output
:Publisher
객체가 내보낼 값의 타입Failure
:Error
가 발생했을 때Error
타입(Error
를 방출하고 싶지 않으면,Never
로 설정)receive(subscriber:)
:Publisher
와 연결될subscriber
를 받는 메서드
근데 receive(subscriber:)
와 비슷한 역할을 하는 메서드가 있다.
그건 바로 subscribe(_:)
이 녀석 공식문서를 보면, 애 역시 지정된 subscriber
를 이 게시자에 연결하는 역할 공식문서에 의하면 receive(subscriber:)
대신 subscribe(_:)
메서드를 이용하라고 나와있다.
애플은 자주 사용될것 같은 Publisher
객체를 미리 구현해 두었는데 리스트로 보면
- Just
- Future
- Deferred
- Empty
- Fail
- Record
- AnyPublisher
Just
A publisher that emits an output to each subscriber just once, and then finishes.
Just는 한번의 아웃풋만을 전달하는 publisher객체
Just
가장 심플한 Publisher
객체 Failure
는 Never로 Error를 방출하지 않으며, 딱 한번의 값만을 방출한다.
사용 방법은
let publisher = Just("Combine 첫 교시")
let subscriber = publisher.sink { completion in
switch completion {
case .finished:
print("finished")
}
} receiveValue: { value in
print(value)
}
//Combine 첫 교시
//finished
위와 같이 매우 간단!! 한번의 아웃풋을 방출하고, finished
를 통해 종료 이벤트를 내보낸다.
Future
A publisher that eventually produces a single value and then finishes or fails.
하나의 결과를 비동기로 생성한 뒤 completion event를 보냅니다.
Future
같은 경우 특징은 이니셜라이저에서 값이 방출될때 호출되는 클로져를 받는다.
때문에 클로져 내부에서 Promise
라는 클로져를 호출해 success
시 값을, failure
시 error를 방출한다.
또한 클로져이기 때문에 비동기적으로 로직을 구현할 수 있다.
public typealias Promise = (Result<Output, Failure>) -> Void
enum MyError: Error {
case error
}
let futurePublisher = Future<String, MyError> { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
promise(.success("Combine"))
//에러를 방출해야 할 경우
//promise(.failure(.error))
}
}
let subscriber = futurePublisher.sink { completion in
switch completion {
case .finished:
print("종료")
case .failure(let error):
print(error)
}
} receiveValue: { value in
print(value)
}
print("비동기적으로 구현 가능")
/*
비동기적으로 구현 가능
Combine
종료
*/
promise()
클로져를 DispatchQueue
의 asyncAfter
으로 묶었기 때문에 promise()
클로져가 비동기적으로 호출되어 아래서 호출한 print
가 먼저 찍히는것을 볼 수 있다.
Deferred
A publisher that awaits subscription before running the supplied closure to create a publisher for the new subscriber.
새로운 Subscriber의 Publisher를 만들기 위해 제공된 클로저를 실행하기 전에 Subscription을 기다리는 Publisher
Deferred
의 뜻은 "지연된" 모가 벌써부터 lazy
키워드와 비슷한 느낌??
Deferred
사실 lazy
랑 비슷한 녀석인게 맞다.
그래서 좀 특이한 특징이 Publisher
를 방출하는 Publisher
라는 것 이 녀석도 생성자가 클로져인데 Publisher
를 반환하는 클로져이다.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Deferred<DeferredPublisher> : Publisher where DeferredPublisher : Publisher {
public typealias Output = DeferredPublisher.Output
public typealias Failure = DeferredPublisher.Failure
public let createPublisher: () -> DeferredPublisher
public init(createPublisher: @escaping () -> DeferredPublisher)
public func receive<S>(subscriber: S) where S : Subscriber, DeferredPublisher.Failure == S.Failure, DeferredPublisher.Output == S.Input
}
위처럼 평소에 output타입이 들어가는 제네릭 자리에도 Publisher
타입이 들어가는 것을 볼 수 있다.
직접 사용해 보면,
print("deferredPublisher 생성")
let deferredPublisher = Deferred<Just<String>>(createPublisher: {
print("justPublisher 생성")
return Just("Combine")
})
Thread.sleep(forTimeInterval: 5)
let subscriber = deferredPublisher.sink { completion in
switch completion {
case .finished:
print("종료")
}
} receiveValue: { value in
print(value)
}
결과 같이 deferredPublisher
가 생성되었고, 실제 값을 방출하는 justPublisher
는 실제 사용될 때(5초 뒤)에 생성되는 것을 볼 수 있다. 즉, 구독되었을 때 생성된다.
Empty
A publisher that never publishes any values, and optionally finishes immediately.
아무런 값도 내보내지 않고 즉시 completion 이벤트를 보낼지 선택할 수 있는 Publisher
Empty
의 특징은 값을 방출하지 않는 Publisher
이다. 오직 completion
만 호출 하지만, 생성자에서 completeImmediately
를 false
로 하면 completion
마져 실행하지 않음(기본값은 true
)
let emptyPublisher = Empty<Void, Never>(completeImmediately: true)
let subscriber = emptyPublisher.sink { completion in
switch completion {
case .finished:
print("종료")
case .failure(let error):
print(error)
}
} receiveValue: { value in
print(value)
}
// 종료
Fail
A publisher that immediately terminates with the specified error.
Error를 즉시 보낼 수 있는 Publisher
Fail
의 특징은 Error
만을 방출하는 Publisher
이다. 몬가 Just
에 Error 버전???Fail
또한 Just
만큼 간단하기 때문에 바로 코드로 보쟈
enum MyError: Error {
case error
}
let failPublisher = Fail<Void, MyError>(error: .error)
let subscriber = failPublisher.sink { completion in
switch completion {
case .finished:
print("종료")
case .failure(let error):
print(error)
}
} receiveValue: { value in
print(value)
}
//error
Fail
는 생성자로 방출할 Error
를 받는다 Just
가 방출할 값을 받는 것 처럼
Record
A publisher that allows for recording a series of inputs and a completion, for later playback to each subscriber.
subscriber가 나중에 재생할 수 있도록 일련의 입력 및 완료를 기록할 수 있는 publisher
Record
의 특징은 subscriber
가 받을 intput
과 completion
미리 저장해 두었다가 방출한다. 때문에 내부적으로
public struct Recording {
public typealias Input = Output
public var output: [Output] { get }
public var completion: Subscribers.Completion<Failure> { get }
public init()
public init(output: [Output], completion: Subscribers.Completion<Failure> = .finished
public mutating func receive(_ input: Record<Output, Failure>.Recording.Input)
public mutating func receive(completion: Subscribers.Completion<Failure>)
}
위와 같이 Recording
라는 구조체를 가지고 있다.
직접 사용해 보면,
enum MyError: Error {
case error
}
let recordPublisher = Record<String, MyError>(output: ["Combine", "RxSwift"], completion: .finished)
//let recordPublisher = Record<String, MyError> { record in
// record.receive("Combine")
// record.receive("RxSwift")
// record.receive(completion: .finished)
//}
let subscriber = recordPublisher.sink { completion in
switch completion {
case .finished:
print("종료")
case .failure(let error):
print(error)
}
} receiveValue: { value in
print(value)
}
/*
Combine
RxSwift
종료
*/
위와 같이 사용할 수 있다.
사용 방법은 비교적 간단...
AnyPublisher
A publisher that performs type erasure by wrapping another publisher.
publisher를 AnyPublisher타입으로 래핑하여 기존 publisher의 타입을 지워주는 publisher
AnyPublisher
는 말 그래도 publisher
의 Any
이다.eraseToAnyPublisher()
메서드를 통해 publisher
의 타입을 AnyPublisher
타입으로 변경할 수 있다. 즉, publisher
의 타입을 추상화할 수 있다.
let recordPublisher = Record<String?, MyError>(output: ["Combine",nil, "RxSwift"], completion: .finished)
만약, 위같은 상황에서 nil을 제외한 값만 방출하고 싶은 경우 AnyPublisher
를 이용하여 해결할 수 있다.
let anyPublisher = recordPublisher
.flatMap { value -> AnyPublisher<String, Never> in
if let value = value {
return Just(value).eraseToAnyPublisher()
} else {
return Empty().eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
let subscriber = anyPublisher.sink { completion in
switch completion {
case .finished:
print("종료")
case .failure(let error):
print(error)
}
} receiveValue: { value in
print(value)
}
/*
Combine
RxSwift
종료
*/
위 같이 flatMap
의 반환값을 AnyPublisher
로 설정하고, 값이 있을 때는 Just
를 AnyPublisher
로 래핑하여 반환하고, nil
이면 값을 방출하지 않는 Empty
를 AnyPublisher
로 래핑하여 반환한다.
이로써 AnyPublisher
를 이용하여 서로다른 타입의 publisher
를 반환할 수 있게 된다.
'Swift' 카테고리의 다른 글
Combine 뽀개기 3장: Subject (0) | 2023.07.26 |
---|---|
Combine 뽀개기 2장: Subscriber (0) | 2023.07.26 |
[Swift] Swift Concurrency와 GCD (1) | 2022.12.14 |
[Swift] Race Condition과 Thread Safe (0) | 2022.12.12 |
[Swift] Protocol (0) | 2022.02.23 |