iOS/Combine

@Published와 Subject 차이점

Hamp 2024. 9. 24. 14:45
반응형

 

배경

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 기능을 사용할 수 있다는 뜻 

아래 사진을 보면 조금더 이해가 될꺼다.

 

여기서 중요한 점은 @PublishedwillSet 옵저버에서 퍼블리싱이 발생한다.

이 퍼블리시 시점인 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개 꽂힘
}

 


차이점

  1. 프로토콜에서 property wrapper 사용할 없기때문에 Publised변수는 protocol 추상화를 없다.
  2. @Published 클래스의 프로퍼티에만 사용할 있고, structure 같은 non-class 타입에서는 사용이 제한되지만 Subject struct같은 non-class 타입에서도 사용이 가능하다.
  3. @Published private(set)으로 설정함으로 인해서 외부에서 값을 직접 설정하는 것을 방지할 있지만 subject 경우는 send 막을 방법이 없다
  4. 값의 방출 시점이 달라 @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에게 전달하게된다


참고

 

Rxswift vs Combine 개념편( 3 )

안녕하세요. iOS팀 함프입니다. 이번 시간은 Subject라는 개념에 대하여 정리해 보겠습니다. Rxswift와 Combine을 따로 구분하지 않은 이유는 운명처럼 두 프레임워크 모두 같은 개념으로 Subject 이름을

blog.wakmusic.xyz

 

 

[iOS]@Published와 Subject중 뭘 선택해야할까?

기존 MVC의 프로젝트를 MVVM으로 리팩터링을 준비하는 과정에서 유저의 input에 의해 변화하는 변수를 @Published로 만들어서쓸지 아니면 subject로 쓸지를 고민하게 되었습니다. 처음에 제 머릿속에 들

velog.io

 

 

ObservableObject | Apple Developer Documentation

A type of object with a publisher that emits before the object has changed.

developer.apple.com

 

 

Published | Apple Developer Documentation

A type that publishes a property marked with an attribute.

developer.apple.com

 

반응형