study record

[Swifit] Combine 기본 예제 본문

Swift/스위프트 정리

[Swifit] Combine 기본 예제

asong 2023. 1. 15. 17:48

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