Receiving and Handling Events with Combine

Horus-iOS·2022년 12월 27일
0

Combine

목록 보기
1/9

https://developer.apple.com/documentation/combine/receiving-and-handling-events-with-combine

Customize and receive events from asynchronous sources.

비동기 소스로부터 이벤트를 커스터마이징 하고 받습니다.

Overview

컴바인 프레임워크는 앱이 이벤트를 어떻게 처리할지에 대한 선언적 접근방법을 제공합니다. 여러 딜리게이트 콜백 혹은 컴플리션 핸들러 클로저를 구현하는 것을 대신해, 주어진 이벤트 소스를 위한 하나의 연쇄적 처리를 생성할 수 있습니다. 연쇄의 각 부분은 이전 단계로부터 받은 요소들을 구분하면서 액션을 수행하는 컴바인 연산자입니다.

텍스트 필드의 컨텐츠를 활용하는 테이블 혹은 컬렉션 뷰에 필터가 필요한 앱을 생각해볼 수 있습니다. 앱킷(AppKit)에서 텍스트 필드에 대한 각각의 키 입력은 컴바인과 함께 구독할 수 있는 노티피케이션을 제공합니다. 노티피케이션을 받은 후 연산자를 사용해 컨텐트 및 이벤트 전달 시점을 변경할 수 있으며, 앱의 UI를 업데이트 하기 위한 최종 결과물을 사용할 수 있습니다.

Connect a Publisher to a Subscriber

컴바인과 함께 텍스트 필드의 노티피케이션을 받으려면 노티피케이션 센터의 기본값 인스턴스에 접근하고, 이 인스턴스의 publisher(for:object:) 메소드를 호출하면 됩니다. 이 호출은 노티피케이션 이름과 노티피케이션을 원하는 소스 객체를 받을 것이며, 노티피케이션 요소들을 제공하는 퍼블리셔를 반환할 것입니다.

let pub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)

퍼블리셔로부터 요소들을 받으려면 Subscriber를 사용할 수 있습니다. Subscriber는 associated type, Input을 정의하며, 이들은 받고자하는 것에 대한 타입을 선언하기 위함입니다. 퍼블리셔 역시 타입, Output을 정의하며, 생성하고자 하는 것을 선언하기 위함입니다. 퍼블리셔와 Subscriber 모두 타입, Failure를 정의하며, 이들은 생성하거나 받는 오류의 종류를 나타내기 위한 것입니다. Subscriber를 제공자에게 연결하려면 Output이 반드시 Input과 일치해야 하며, Failure 타입 역시 일치해야 합니다.

컴바인은 두 가지 Subscriber를 제공합니다. 이들은 퍼블리셔에 내제된 출력과 실패 타입에 일치시키는 것들입니다.

  • sink(receiveCompletion:receiveValue:)는 두 가지 클로저를 갖습니다. 첫 번째 클로저는 Subscribers.Completion을 받을 때 실행하는 클로저이며, 퍼블리셔가 정상적으로 완료되었는지 혹은 오류와 함께 실패했는지를 나타냅니다. 두 번째 클로저는 퍼블리셔로부터 요소를 받을 때 실행합니다.

  • assign(to:on:)은 받은 모든 요소를 즉각적으로 주어진 객체에 할당하며, 프로퍼티를 나타내기 위한 키 경로를 사용합니다.

예를 들어 퍼블리셔가 완료되었을 때 로그를 나타내기 위해 sink Subscriber를 사용할 수 있으며, 각 시점에 요소를 받습니다.

let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)
    .sink(receiveCompletion: { print ($0) },
          receiveValue: { print ($0) })

sink(receiveCompletion:receiveValue:)assign(to:on:) 모두 퍼블리셔로부터 제한이 없는 수의 요소들을 요청할 수 있습니다. 요소들을 받을 때 rate을 제어하려면 Subscriber 프로토콜을 구현해서 Subscriber를 직접 생성하시기 바랍니다.

Change the Output Type with Operators

이전 섹션에서 살펴본 sink Subscriber는 receiveValue 클로저에서 모든 작업을 수행합니다. 이 사실은 받은 요소들에 대한 커스텀 작업이 많은 경우에도 수행이 필요하다거나 호출 사이에서 상태를 유지하게 되는 경우 부담이 될 수 있습니다. 컴바인의 이점은 이벤트 전달을 커스터마이징 하기 위한 연산자를 조합할 수 있다는 점입니다.

예를 들어 텍스트 필드의 스트링 값만 필요한 것이라면 NotificationCenter.Publisher.Output은 콜백에서 받기 위한 편의 타입이 아닙니다. 퍼블리셔의 아웃풋은 본질적으로 시간 흐름에 따른 요소들의 연속(Sequence)이기 때문에 컴바인은 연속적으로 수정이 가능한 연산자를 제공하며, map(_:), flatMap(maxPublishers:_:), reduce(_:_:)와 같은 것들이 있습니다. 이 연산자들의 동작은 스위프트 표준 라이브러리에 있는 것들과 유사합니다.

퍼블리셔의 아웃풋 타입을 변경하려면 클로저가 다른 타입을 반환하는 map(_:) 연산자를 추가할 수 있습니다. 이 경우 NSTextField인 노티피케이션의 객체를 얻을 수 있으며, 필드의 stringValue를 얻을 수 있습니다.

let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)
    .map( { ($0.object as! NSTextField).stringValue } )
    .sink(receiveCompletion: { print ($0) },
          receiveValue: { print ($0) })

퍼블리셔 연쇄가 원하는 타입을 제공한 후 sink(receiveCompletion:receiveValue:)assign(to:on:)으로 대체합니다. 아래 예시는 퍼블리셔 연쇄로부터 스트링들을 받고, 이들을 커스텀 뷰 모델 객체의 filterString에 할당하는 예시입니다.

let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)
    .map( { ($0.object as! NSTextField).stringValue } )
    .assign(to: \MyViewModel.filterString, on: myViewModel)

Customize Publishers with Operators

퍼블리셔 인스턴스를 확장해서 원하는 기능을 수행하도록 하는 연산자를 갖도록 할 수도 있습니다. 이벤트 처리 연쇄를 향상시키기 위한 연사자를 사용할 수 있도록 해주는 세 가지 방법이 있습니다.

  • 텍스트 필드를 위한 뷰 모델 스트링을 업데이트하는 것이 아니라 특정 길이보다 짧은 인풋을 무시하기 위해 filter(_:)를 사용하거나 알파벳, 숫자가 아닌 글자를 받지 못하도록 할 수 있습니다.

  • 큰 데이터베이스 쿼리처럼 필터 동작의 비용이 높은 경우 사용자의 입력 멈추길 기다려야 할 것입니다. 이를 위해 퍼블리셔가 이벤트를 내보내기 전에 멈출 수 있도록 해주는 최소한의 시간을 설정하고자 debounce(for:scheduler:options:) 연산자를 사용할 수 있습니다. 런루프 클래스는 초 단위에서 천분의 1초 단위까지 구체화할 수 있는 편의를 제공합니다.

  • 결과가 UI를 업데이트한다면, receive(on:options:)을 호출해서 메인 스레드에 콜백을 전달할 수 있습니다. 런루프 클래스에서 제공하는 스케줄러 인스턴스를 첫 번째 파라미터로 구체화해서, Subscriber가 메인 런루프에서 호출하도록 할 수 있습니다.

퍼블리셔 선언은 아래와 같습니다.

let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)
    .map( { ($0.object as! NSTextField).stringValue } )
    .filter( { $0.unicodeScalars.allSatisfy({CharacterSet.alphanumerics.contains($0)}) } )
    .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
    .receive(on: RunLoop.main)
    .assign(to:\MyViewModel.filterString, on: myViewModel)

Cancel Publishing when Desired

퍼블리셔는 정상적이거나 실패한다고 하더라도 완료될 때까지 요소들을 계속 내보냅니다. 퍼블리셔가 더이상 내보내지 않도록 하길 원한다면 이를 취소할 수 있습니다. sink(receiveCompletion:receiveValue:)assign(to:on:)으로 생성된 Subscriber 타입 모두 Cancellable 프로토콜을 구현합니다. cancel() 메소드를 제공하고 있습니다.

sub?.cancel()

커스텀 Subscriber를 생성하고 있다면, 퍼블리셔는 첫 번째로 subscribe 하게 될 때 Subscription 객체를 보내야 합니다. 이를 Subscribtion을 저장하고 이후 취소하고 싶은 시점에 cancel() 메소드를 호출해야 합니다. 커스텀 Subscriber를 생성하는 경우 Cancellable 프로토콜을 구현해야 하며, 저장된 subcription에 호출을 전달하는 cancel() 구현이 필요합니다.

0개의 댓글