study record
[Swifit] Combine 기본 예제 본문
Combine을 어떻게 사용할 수 있는지 기본 예제를 정리해보면서 익혀보자!
기본 자료구조 활용 예제(with .sink)
let publisher = [1, 2, 3, 4].publisher
publisher
.sink { (value) in
print(value)
}
Sequence.publisher 를 활용하여 배열에서 .publisher 프로퍼티를 활용해 간단하게 배열의 publisher를 구성할 수 있다.
.sink를 통해 이를 구독하여 값들을 받아 볼 수 있다.
.assign
class Dumper {
var value = 0 {
didSet {
print("value was updated to \(value)")
}
}
}
let dumper = Dumper()
let publisher = [1, 2, 3, 4].publisher
publisher
.assign(to: \.value, on: dumper)
Dumper 클래스는 value에 값이 할당되면 이를 프린트한다.
.assign을 통해 publisher를 구독하고 있고, 코드가 실행되면 값이 콘솔에 프린트될 것이다.
assign은 단어 자체의 뜻처럼 원하는 곳에 값을 할당하고자 할 때 사용하면 좋은 것 같다.
@Published
@Published Property Wrapper를 통해 publisher를 구성할 수 있다.
제약 사항은 클래스의 프로퍼티에만 사용할 수 있다는 것이다.
class SomeModel {
@Published var name = "Test"
}
let model = SomeModel()
let publisher = model.$name
publisher
.sink(receiveValue: { value in
print("name is \(value)")
SomeModel 클래스는 @Published Property Wrapper가 붙은 프로퍼티를 가진다.
이를 publisher로 활용하려면 다음과 같이 프로퍼티 앞에 $를 붙여 사용한다.
이렇게 되면 model.name에 값이 할당될 때마다 즉 변경될 때마다 publish되고, .sink를 통해 값을 받아볼 수 있다.
초기값도 받게 된다.
AnyPublisher
또다른 publisher를 감싸는 것에 의해 타입을 숨기는 publisher.
@frozen struct AnyPublisher<Output, Failure> where Failure : Error
AnyPublisher는 스스로의 프로퍼티를 가지지 않고 업스트림 publisher로부터 요소들과 completion 값을 전달하는 Publisher의 구체적 시행이다.
API를 통해 디테일한 타입을 노출하고 싶지 않을 때 publisher를 랩핑하며 AnyPublisher를 사용할 수 있다.
그리고 AnyPublisher와 함께 publisher를 wrap하고자한다면 eraseToAnyPublisher()를 사용하면 된다.
func eraseToAnyPublisher() -> AnyPublisher<Self.Output, Self.Failure>
public class TypeWithSubject {
public let publisher: some Publisher = PassthroughSubject<Int,Never>()
}
public class TypeWithErasedSubject {
public let publisher: some Publisher = PassthroughSubject<Int,Never>()
.eraseToAnyPublisher()
}
// In another module:
let nonErased = TypeWithSubject()
if let subject = nonErased.publisher as? PassthroughSubject<Int,Never> {
print("Successfully cast nonErased.publisher.")
}
let erased = TypeWithErasedSubject()
if let subject = erased.publisher as? PassthroughSubject<Int,Never> {
print("Successfully cast erased.publisher.")
}
// Prints "Successfully cast nonErased.publisher."
위 예시와 같이 .eraseToAnyPublisher()를 사용하면 publisher를 다른 타입으로 바꿀 수 없음을 알 수 있다.
즉, Subscriber에게 데이터를 전달할 때 그 타입이 복잡해짐을 숨기고자 할 때 사용될 수 있다.
AnyCancellable
final class AnyCancellable
취소할 때 제공되는 클로저를 실행하는 type-erasing cancellable 객체.
AnyCancellable 인스턴스는 자동적으로 deinit될 때 cancel()을 호출한다.
따라서 Cancellable 인스턴스와 다르게 굳이 cancel()을 호출할 필요가 없다.
*cancel()작업이란 현재 사용하는 자원에 대해 해제를 의미한다.
class ViewController: UIViewController {
var cancellable: AnyCancellable?
deinit {
print("viewController deinit")
}
override func viewDidLoad() {
super.viewDidLoad()
let stream = Publishers.Just("is Alive?")
.delay(for: .seconds(5), scheduler: RunLoop.main)
.sink(receiveCompletion: { (completion) in
switch completion {
case .finished: print("finished")
case .failure(let error): print(error)
}
}, receiveValue: {
print($0)
})
cancellable = AnyCancellable(stream)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
UIApplication.shared.keyWindow?.rootViewController = nil
}
}
<output>
viewController deinit
deinit될 때 자동으로 cancel된다.
store
final func store(in set: inout Set<AnyCancellable>)
type-erasing cancellable 인스턴스를 저장한다.
set은 취소가능한 인스턴스를 저장할 집합을 뜻한다. Rx의 disposeBag과 유사하다.
var cancellable = Set<AnyCancellable>()
let subject = PassthroughSubject<Int, Never>()
subject
.sink(receiveValue: { })
.store(in: &cancellable)
다음과 같이 sink를 통해 리턴된 AnyCancellable을 저장한다.
이렇게 되면 deinit될 때 자동으로 set이 cancel()하여 메모리에 남아있지 않게 한다.
참고
- https://seorenn.tistory.com/81
- https://developer.apple.com/documentation/combine/anypublisher
- RxSwift Combine 비교글(이해하는 데에 도움을 많이 주는 글입니다!)
- https://eunjin3786.tistory.com/67
'Swift > 스위프트 정리' 카테고리의 다른 글
[Swift] Swift 고차함수(map 시리즈, reduce, filter) (0) | 2023.01.30 |
---|---|
[Swift] Combine 연산자와 CheckedContinuation (0) | 2023.01.29 |
[Swift] Actor란? - 1 (0) | 2022.12.18 |
[Swift] SwiftUI란? (0) | 2022.11.23 |
[Swift] Concurrency 사용하기 (0) | 2022.11.15 |