Combine Framework FREE course: write you first iOS app - use Subscriptions & Publishers like Subject
CurrentValueSubject
: 초깃값이 존재하는 퍼블리셔PassthroughSubject
: 초깃값이 없는 퍼블리셔@Published
: 퍼블리시 프로토콜을 따른다는 의미sink
를 통해 해당 값의 변화를 스트리밍 가능ObservableObject
등 프로토콜과 함께 사용할 때 해당 클래스 (즉 해당 프로토콜을 채택하고 내부에서 퍼블리셔를 통해 변화를 체크할 데이터를 가지고 있는 클래스) 자체의 변화 또한 전달 가능struct UserModel {
var id: Int
var name: String
}
let currentUserId = CurrentValueSubject<Int, Never>(1000)
// get the value for currentUserId
print("currentUserId \(currentUserId.value)")
// subscribe to Subject
currentUserId
.sink { value in
print("Received value: \(value)")
}
// passing down new values with Subject
currentUserId.send(999)
currentUserId.send(100)
// sending completion finished with Subject
currentUserId.send(completion: .finished)
// currentUserId.send(10) -> Finished data stream
CurrentValueSubject
에 send
메소드를 통해 데이터 스트림을 시작, sink
를 통해 마지막 단에서 데이터를 캐치send
의 completion
을 통해 해당 스트림 자체를 핸들링 가능let newUserNameEntered = PassthroughSubject<String, Never>()
// get the value for newUserNameEntered
// does not hold a value newUserNameEntered.value
// subscribe to Subject
newUserNameEntered
.sink { value in
print("Received value: \(value)")
}
// passing down new values with Subject
newUserNameEntered.send("Hello")
newUserNameEntered.send("World")
// sending completion finished with Subject
newUserNameEntered.send(completion: .finished)
PassthroughSubject
class ViewModel {
private let userNamesSubject = CurrentValueSubject<[String], Never>(["Bill"])
// Object with Read + Write -> Private
var userNames: AnyPublisher<[String], Never>
// Read Only
let newUserNameEntered = PassthroughSubject<String, Never>()
var cancellables = Set<AnyCancellable>()
init() {
// create publisher stream that updates whenever a newUserNameEntered has a new value
userNames = userNamesSubject.eraseToAnyPublisher()
newUserNameEntered
.sink
{ [weak self] userName in
guard let self = self else { return }
self.userNamesSubject.send(self.userNamesSubject.value + [userName])
}
.store(in: &cancellables)
userNames
.sink { users in
print("usernames changed to \(users)")
}
.store(in: &cancellables)
}
}
let viewModel = ViewModel()
viewModel.userNames.sink { names in
print(names)
}
// userNames -> Read Only
// add new User name
viewModel.newUserNameEntered.send("Junyeong")
viewModel.newUserNameEntered.send("Sujeong")
/*
usernames changed to ["Bill"]
["Bill"]
usernames changed to ["Bill", "Junyeong"]
["Bill", "Junyeong"]
usernames changed to ["Bill", "Junyeong", "Sujeong"]
["Bill", "Junyeong", "Sujeong"]
*/
private
으로 사용하는 퍼블리셔 및 외부에서 접근 가능한 읽기 전용 퍼블리셔 구별newuserNameEntered
퍼블리셔는 외부에서 send
를 통해 데이터를 인풋으로 줄 수 있음userNames
는 send
불가능, sink
만 가능한 단방향 스트림class ViewModel {
// use @Published to create Publisher
@Published var userNames: [String] = ["Bill"] {
willSet {
print("\(newValue) has come!")
}
}
// Read Only
let newUserNameEntered = PassthroughSubject<String, Never>()
var cancellables = Set<AnyCancellable>()
init() {
$userNames
.sink { [weak self] names in
guard let self = self else { return }
print("Received value: \(names)")
}
.store(in: &cancellables)
newUserNameEntered
.sink { [weak self] name in
guard let self = self else { return }
self.userNames.append(name)
}
.store(in: &cancellables)
}
}
let viewModel = ViewModel()
viewModel.newUserNameEntered.send("Susan")
// When the property changes, publishing occurs in the property's willSet block, meaning that subscribers receive the new value before it's actually set on the property
/*
Received value: ["Bill"]
["Bill", "Susan"] has come!
Received value: ["Bill", "Susan"]
*/
Published
프로토콜을 따르는 퍼블리셔willSet
블럭 안에서 퍼블리싱이 발생newUserNameEntered
에 새로운 데이터 입력 → userNames
퍼블리셔의 willSet
블럭 동작 → userNames
퍼블리셔의 sink
를 통해 데이터 스트림 말단부에서 해당 값을 sink
로 받음class ViewModel: ObservableObject {
@Published var userNames = ["Bill", "Joe", "Jean"]
let userNamesSubject = CurrentValueSubject<[String], Never>(["Bill", "Joe", "Jean"])
var cancellables = Set<AnyCancellable>()
init() {
addSubscriber()
}
private func addSubscriber() {
// subscribe to publisher
$userNames
.sink { [unowned self] names in
print("Last: \(self.userNames) - Received: \(names)")
}
.store(in: &cancellables)
userNamesSubject
.sink { [unowned self] name in
print("Last: \(self.userNamesSubject.value) - Received: \(name)")
self.objectWillChange.send()
}
.store(in: &cancellables)
}
}
let viewModel = ViewModel()
viewModel.objectWillChange.sink { _ in
print("ObjectWillChange was sent")
}
viewModel.userNames.append("Moana")
viewModel.userNamesSubject.send(["Elsa"])
/*
Last: ["Bill", "Joe", "Jean"] - Received: ["Bill", "Joe", "Jean"]
Last: ["Bill", "Joe", "Jean"] - Received: ["Bill", "Joe", "Jean"]
ObjectWillChange was sent
Last: ["Bill", "Joe", "Jean"] - Received: ["Bill", "Joe", "Jean", "Moana"]
Last: ["Elsa"] - Received: ["Elsa"]
ObjectWillChange was sent
*/
SwiftUI
의 ObservableObject
프로토콜을 채택한 뷰 모델Published
된 값의 변화를 그대로 감지, objectWillChange
등 변화하기 이전에 특정한 블럭을 실행 가능Published
프로토콜을 따르는 userNames
값이 변화할 때에는 자동으로 objectWillChange
감지 가능CurrentValueSubject
, 즉 Published
프로토콜이 아닌 다른 퍼블리셔의 새로운 값 변화를 감지해야 할 때 ObjectWillChange
를 주기 위해서는 직접적으로 self.objectWillChange.send()
를 sink
블럭 안에 작성해야 할 필요가 있음