Publishers, Subscribers
- 데이터의 흐름을 관찰하고 반응하는 데에 주로 사용되며, 변화하는 데이터를 생성하고 해당 데이터에 대한 응답을 처리
Publisher, Subscriber
Publisher
는 데이터 스트림을 생성하는 타입으로, 특정 데이터 소스에서 새로운 값이나 이벤트를 발생시킬 수 있다.
Subscriber
는 Publisher
에서 발생한 데이터 스트림을 수신하는 타입입니다.
class SubscriberViewModel: ObservableObject {
@Published var count: Int = 0
var timer: AnyCancellable?
init() {
setUpTimer()
}
func setUpTimer() {
timer = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.count += 1
if let count = self?.count, count >= 10 {
self?.timer?.cancel()
}
}
}
}
Cancellable
은 취소 가능한 작업을 나타내는 프로토콜 객체이며, cancel
메서드를 이용해 Publisher
가 값을 발생시키는 것을 취소하고 리소스에 대해 해제할 수 있다.
AnyCancellable
은 하나 이상의 Cancellable
인스턴스를 저장하는 클래스이며, 필요에 따라 모든 인스턴스를 취소할 수 있다.
store
메서드를 이용해서 취소 가능한 인스턴스를 Set
에 저장할 수 있다.(RxSwift
의 disposeBag
과 비슷한 개념)
class SubscriberViewModel: ObservableObject {
@Published var count: Int = 0
var cancellables = Set<AnyCancellable>()
init() {
setUpTimer()
}
func setUpTimer() {
Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard let self = self else { return }
self.count += 1
if self.count >= 10 {
for item in self.cancellables {
item.cancel()
}
}
}
.store(in: &cancellables)
}
}
map
메서드를 이용하여 입력 스트림에서 값을 가져와서 다른 형식이나 다른 값으로 변환할 수 있다.
assign
메서드를 이용하여 Publisher
의 값을 특정 속성에 할당할 수 있다.
sink
메서드에서는 [weak self]
를 사용하여 메모리 누수를 방지할 수 있다.
debounce
메서드를 이용하여 최신 값 전달을 입력이 없는 주어진 시간동안 지연시킬 수 있다.
class SubscriberViewModel: ObservableObject {
var cancellables = Set<AnyCancellable>()
@Published var textFieldText: String = ""
@Published var textIsValid: Bool = false
init() {
addTextFieldSubscriber()
}
func addTextFieldSubscriber() {
$textFieldText
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.map { text -> Bool in
if text.count > 3 {
return true
}
return false
}
.sink(receiveValue: { [weak self] isValid in
self?.textIsValid = isValid
})
.store(in: &cancellables)
}
}
combineLatest
메서드를 이용해서 두 개 이상의 Publisher
를 결합하여, 각 Publisher
의 최신 값을 기반으로 새로운 값을 생성할 수 있다.
class SubscriberViewModel: ObservableObject {
@Published var count: Int = 0
var cancellables = Set<AnyCancellable>()
@Published var textFieldText: String = ""
@Published var textIsValid: Bool = false
@Published var showButton: Bool = false
init() {
setUpTimer()
addTextFieldSubscriber()
addButtonSubscriber()
}
func setUpTimer() {
Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard let self = self else { return }
self.count += 1
}
.store(in: &cancellables)
}
func addTextFieldSubscriber() {
$textFieldText
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.map { text -> Bool in
if text.count > 3 {
return true
}
return false
}
.sink(receiveValue: { [weak self] isValid in
self?.textIsValid = isValid
})
.store(in: &cancellables)
}
func addButtonSubscriber() {
$textIsValid
.combineLatest($count)
.sink { [weak self] isValid, count in
guard let self = self else { return }
if isValid && count >= 10 {
self.showButton = true
} else {
self.showButton = false
}
}
.store(in: &cancellables)
}
}