[SwiftUI][Combine] @Published, PassthroughSubject, CurrentValueSubject 차이

후발주자라 그런지 Rx와 비슷한 방식이 적용됐다. @published, PassthroughSubject, CurrentValueSubject의 차이를 알아보기 위해 예제를 만들어 보자.

class TestEnvironment: ObservableObject {
  @Published var value1 = "aaa"
  @Published var value2 = PassthroughSubject<String, Never>()
  @Published var value3 = CurrentValueSubject<String, Never>("aaa")
}
// SceneDelegate.swift
let testEnvironment = TestEnvironment()
let contentView = ContentView().environmentObject(testEnvironment)
struct ContentView: View {
  @EnvironmentObject var testEnvironment: TestEnvironment
  @State var cancellable = Set<AnyCancellable>()

  var body: some View {
    VStack {
      Button(action: {
        print("--------- click-aaa ---------")
        self.testEnvironment.value1 = "aaa"
        self.testEnvironment.value2.send("aaa")
        self.testEnvironment.value3.send("aaa")

      }) { Text("Button aaa") }

      Button(action: {
        print("--------- click-bbb ---------")
        self.testEnvironment.value1 = "bbb"
        self.testEnvironment.value2.send("bbb")
        self.testEnvironment.value3.send("bbb")

      }) { Text("Button bbb") }
    }.onAppear {
      print("onAppear =============== ")
      self.testEnvironment.$value1
        .filter { String($0) == "aaa" }
        .sink { print("value1-aaa: \($0)") }
        .store(in: &self.cancellable)

      self.testEnvironment.value2
        .filter { String($0) == "aaa" }
        .sink { print("value2-aaa: \($0)") }
        .store(in: &self.cancellable)

      self.testEnvironment.value3
        .filter { String($0) == "aaa" }
        .sink { print("value3-aaa: \($0)") }
        .store(in: &self.cancellable)

      self.testEnvironment.$value1
        .filter { String($0) == "bbb" }
        .sink { print("value1-bbb: \($0)") }
        .store(in: &self.cancellable)

      self.testEnvironment.value2
        .filter { String($0) == "bbb" }
        .sink { print("value2-bbb: \($0)") }
        .store(in: &self.cancellable)

      self.testEnvironment.value3
        .filter { String($0) == "bbb" }
        .sink { print("value3-bbb: \($0)") }
        .store(in: &self.cancellable)
    }
  }
}
onAppear =============== 
value1-aaa: aaa
value3-aaa: aaa
--------- click-aaa ---------
value1-aaa: aaa
value2-aaa: aaa
value3-aaa: aaa
--------- click-aaa ---------
value1-aaa: aaa
value2-aaa: aaa
value3-aaa: aaa
--------- click-bbb ---------
value1-bbb: bbb
value2-bbb: bbb
value3-bbb: bbb
--------- click-bbb ---------
value1-bbb: bbb
value2-bbb: bbb
value3-bbb: bbb

결론

@Published + 일반 변수 타입

  • 일반 변수를 구독형으로 만들어 줌
  • 단 사용할 때 변수 앞에 $를 붙여줘야 함
  • 구독 시작하자마자 발행이 되기 때문에 Rx의 BehaviorSubject와 비슷해짐
  • 초기값이 없으면 실제 사용하는 곳에서 무지 귀찮아지니까 차라리 PassthroughSubject로 바꾸는게 좋아보임

PassthroughSubject

  • Rx의 PublishedSubject와 비슷
  • 구독 후 값이 들어왔을 때만 발행됨
  • 같은 값이 또 들어와도 발행됨

CurrentValueSubject

  • Rx의 BehaviorSubject와 비슷
  • 구독하자마자 현재 값이 발행됨
  • 무조건 초기값을 지정해야 함