배경
tableview에서 값을 삭제하는데 다음과 같은 버그로 앱이 종료되었다.
// IssuelistViewModel.swift
@Published var issues: [IssueResponseDTO] = []
// IssuelistViewController.swift
viewModel.$issues
.withUnretained(self)
.sink { (owner, issues) in
print("\(#file) \(#function) : \(issues.count)") // 삭제가 잘되어 3개 꽂힘
owner.tableView.reloadData()
}
.store(in: &subscription)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("\(#file) \(#function) : \(viewModel.issuesSubject.value.count)")
return viewModel.issues.count // 여전히 4개 꽂힘
}
위 구조를 봤을 때 나는 @Publishd issues의 값이 4개에서 1개 삭제되어 3 남은 것을 보고 크게 다를께 없구나라고
생각했지만 issues.count로 접근했을 때는 아직 4개라 talbeview cell 랜더할 때 인덱스 범위를 벗어나 문제가 되었다.
여기서 느낀점은 아 @Published를 아직 완전히 이해하지 못했구나라는 점을 느꼈고 해결한 방법인 subject와 차이점을 알아보자.
@Published
정의
@Published로 표시된 프로퍼티는 값이 바뀌면 외부로 바뀐 값을 퍼블리싱하는 타입이다.
특징
또한 $기호를 이용하면 . 그 프로퍼티의 Publiser에 접근이 가능하다. 즉 Combine 기능을 사용할 수 있다는 뜻
아래 사진을 보면 조금더 이해가 될꺼다.
여기서 중요한 점은 @Published는 willSet 옵저버에서 퍼블리싱이 발생한다.
이 퍼블리시 시점인 willSet이라는 점을 꼭 기억하자.
정리하면
- 값이 바뀌면 외부로 바뀐 값을 퍼블리싱하는 역할
- 그 시점은 willSet 옵저버에서 발생
- class 프로퍼티에만 사용가능, non-class에서는 사용 불가
Subject
정의
send(_:)를 통해 stream에 값을 “주입”할 수 있는 publisher
특징
값을 주입하는 방식이기 때문에 기본 stored property처럼 시점에 따른 side effect가 더 적었다.
이 특징을 살려 다음과 같이 문제를 해결했다.
// IssuelistViewModel.swift
var issuesSubject: CurrentValueSubject<[IssueResponseDTO], Never> = CurrentValueSubject<[IssueResponseDTO], Never>([])
// IssuelistViewController.swift
viewModel.issuesSubject
.withUnretained(self)
.sink { (owner, issues) in
print("\(#file) \(#function) : \(issues.count)") // 삭제된 후 1개 꽂힘
owner.tableView.reloadData()
}
.store(in: &subscription)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("\(#file) \(#function) : \(viewModel.issuesSubject.value.count)")
return viewModel.issuesSubject.value.count // 삭제된 후 1개 꽂힘
}
차이점
- 프로토콜에서 property wrapper를 사용할 수 없기때문에 Publised변수는 protocol로 추상화를 할 수 없다.
- @Published는 클래스의 프로퍼티에만 사용할 수 있고, structure 같은 non-class 타입에서는 사용이 제한되지만 Subject는 struct같은 non-class 타입에서도 사용이 가능하다.
- @Published는 private(set)으로 설정함으로 인해서 외부에서 값을 직접 설정하는 것을 방지할 수 있지만 subject의 경우는 send를 막을 방법이 없다
- 값의 방출 시점이 달라 @Published는 차이가 존재할 수 있다.
차이점을 보게되면 왜 @Published가 필요하지?? 모든게 subject가 유리한데??
그 위력은 swiftUI에서 살펴볼 수 있다.
swiftUI에서 @Published의 위력
swiftUI에는 ObservableObject 라는 protocol이 존재한다
ObservableObject 는 objectWillChange라는 프로퍼티를 사용할 수 있는데해당 프로퍼티에
send()라는 메서드가 존재한다.
해당 메서드는 변경사항을 알려주는 역할을 한다.
근데 우리가 변수가 많아지고 변수의 변경이 잦아지면 매번 send를 호출하기가 쉽지않을거고 실수할가능성이 높을 것 같다.
이런 문제를 해결해줄수있는게 @Published다.
기본적으로 ObjectableObject는 @Published 속성이 변경되기 전에 변경된 값(willSet)을 내보내는
objectWillChange publisher를 synthesize((합성))한다.
ObservableObject 프로토콜을 따르는 객체에서 objectWillChange 프로퍼티를 직접 사용하지 않고
변경 사항을 전달하는 방법이 @Published 인거고 해당 속성을 달아주면 변수의 값이 변경되었을때
자동으로 objectWillChange 프로퍼티를 사용하여 변경 사항을 subscriber에게 전달하게된다
참고