오늘은 Combine을 사용하면서 assign과 sink의 차이가 흥미로워 기록으로 남겨두려고 한다.
먼저 Combine을 사용하면서 이전에 구현했던 방식이 이렇게 구현된다는게 너무 재밌다.. 뭔가 되게 살아있는 무언가를 키우는 느낌이랄까 이런 재미가 있다가 새로운걸 알게 되면 또 이런 재미가 있고.. 아무튼 두가지의 데이터 처리 방법에 대해서 알아보고 어떤 차이가 있는지 살펴보자!
assign
https://developer.apple.com/documentation/combine/publishers/comparison/assign(to:)/
먼저 assign에 대해서 살펴보면 이 메서드를 통해 Publisher의 출력값을 직접 객체의 프로퍼티에 간단하고 직관적이게 할당할 수 있는 기능을 가지고 있다.
퍼블리셔가 새 값을 방출할때 마다 지정된 객체의 특정 프로퍼티에 그 값을 직접 할당한다는 특징이 있다.
또 다른 대표적인 특징은 코드가 짧고 읽기 쉬워 간결해 값을 받아 특정 프로퍼티에 할당하는 단순한 작업에 최적화 되어있다는 점
컴파일 시점에 퍼블리셔의 출력 타입과 할당될 프로퍼티의 타입이 일치하는지 확인하기 때문에 타입 불일치로 인한 런타임 오류를 사전에 방지할 수 있다는 점
직접 프로퍼티에 값을 할당하기 때문에 클로저를 통해 할당하는 것 보다 조금 더 효율 적이라는 점이 있다!
예시를 통해서 살펴보면
class TemperatureMonitor {
@Published var temperature: Double = 0
}
class TemperatureDisplay {
var currentTemperature: Double = 0
private var cancellables = Set<AnyCancellable>()
init(monitor: TemperatureMonitor) {
monitor.$temperature
.assign(to: \\.currentTemperature, on: self)
.store(in: &cancellables)
}
}
온도를 모니터링하는 시스템을 구현한다고 가정할때
@Published var temperature: Double = 0
Published 속성 래퍼를 사용해 프로퍼티를 선언해 값이 변경될 때마다 자동으로 알림을 발행하는 퍼블리셔를 생성한다.
class TemperatureDisplay {
var currentTemperature: Double = 0
private var cancellables = Set<AnyCancellable>()
init(monitor: TemperatureMonitor) {
monitor.$temperature
.assign(to: \\.currentTemperature, on: self)
.store(in: &cancellables)
}
}
currentTemperature 라는 현재 온도를 저장하는 프로퍼티를 선언해주고 메모리 관리를 위해 RxSwift의 disposeBag 역할을 하는 cancellables을 사용해주었다.
그 다음 assign을 활용해 퍼블리셔에서 값이 나오는 것을 currentTemperature에 할당하도록 구현하였다.
이렇게 퍼블리셔에서 직접적으로 값을 할당하면서 간단하고 직관적으로 구현할 수 있다는 점이 특징이다.
추가적으로 이 경우에는 현재 강하게 참조되어 있기 때문에 위에 미리 만들어주었던 cancellables를 store를 통해 선언해주므로써 메모리 누수가 일어나지 않도록 관리할 수 있다.
또한, 에러 핸들링이 불가해지므로써 퍼블리셔가 에러를 방출하면 구독이 즉시 취소되고 이를 별도로 처리할 수 있는 방법이 없다는 점을 잊지말자!
sink
다음은 sink에 대해서 알아보자
https://developer.apple.com/documentation/combine/publisher/sink(receivevalue:)
sink는 assign보다 더욱 유연하고 강력한 구독방식을 제공한다. 퍼블리셔의 이벤트를 받아 처리하는 두 개의 클로저를 제공한다는 점이 특징!
또한, 직접적으로 연결하는 assign과 달리 값을 받아 처리하는 로직을 보다 자유롭게 구현할 수 있고, 값 변형, 필터링 등 부가 작업들의 복잡한 처리가 가능하다는 것이 차이점이자 특징이다!
receiveCompletion 클로저를 활용해 퍼블리셔의 완료 이벤트 즉, 정상적으로 완료 및 오류를 별도로 처리할 수 있다는 특징이 있고 오류 발생하면 적절한 대응이 가능하다는 점이 있다.
추가적으로 예제를 통해 살펴보자면
class TemperatureMonitor {
@Published var temperature: Double = 0
}
class TemperatureDisplay {
var currentTemperature: Double = 0
private var cancellables = Set<AnyCancellable>()
init(monitor: TemperatureMonitor) {
monitor.$temperature
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Temperature monitoring finished")
case .failure(let error):
print("Error: \\(error)")
}
}, receiveValue: { [weak self] newTemperature in
self?.currentTemperature = newTemperature
print("Temperature updated: \\(newTemperature)")
})
.store(in: &cancellables)
}
}
assign과 동일한 온도를 모니터링 하는 과정을 구현하는 예제이다.
위 내용과 동일한 내용을 제외하고
init(monitor: TemperatureMonitor) {
monitor.$temperature
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Temperature monitoring finished")
case .failure(let error):
print("Error: \\(error)")
}
}, receiveValue: { [weak self] newTemperature in
self?.currentTemperature = newTemperature
print("Temperature updated: \\(newTemperature)")
})
.store(in: &cancellables)
}
sink를 활용한 init 메서드를 살펴보면 sink 메서드를 통해 퍼블리셔의 출력을 구독하게 된다.
그리고 두개의 클로저를 받아 유연한 값 처리가 가능하다는 것이 특징!
각각 값이 성공했을때, 실패했을때를 설정해 줄 수 있고 다른 클로저를 활용해 이벤트와 값 처리를 별도로 해줄 수 있다.
receiveCompletion: { completion in
switch completion {
case .finished:
print("Temperature monitoring finished")
case .failure(let error):
print("Error: \\(error)")
}
}
그 다음 에러 핸들링 측면을 보면 finished를 통해 정상적으로 값이 잘 전달되었다면 별도의 이벤트를, 실패했다면 .failure를 통해 오류 값을 전달해 줄 수 있다.
이렇게 sink를 통해 구현하게 되면 temperature 값이 변경될때 마다 currentTemperature 현재의 온도 값이 자동으로 업데이트가 되게 되고, 온도가 업데이트 될 때마다 별도의 로그를 출력해 변경사항을 쉽게 추적할 수 있게 된다.
또한 오류 처리까지 해줄 수 있다는 점이 특징!
이렇게 Combine을 활용해 데이터 흐름을 선언적이고 효율적으로 관리할 수 있게 된다는 점이 너무너무 재밌다..
앞으로 더욱 학습해서 깊고 깊게 알아보고 배워나가자!
오늘은 여기까지!!
'◽️ Programming > T I L' 카테고리의 다른 글
Core Image의 개념과 이미지 내 필터 적용하는 방법 (0) | 2024.08.14 |
---|---|
PHAsset에 대해서 알아보자 (0) | 2024.08.13 |
Result 타입을 활용해 오류처리 하기(Modern CollectionView) (0) | 2024.08.05 |
SwiftData 에 대해서 알아보자 (0) | 2024.08.03 |
ModernCollectionView 구현하기 ( CompositionalLayout , DiffableDataSource , RxSwift ) (0) | 2024.08.01 |