Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

teunteun2

[Combine] Receiving and Handling Events with Combine - developer 문서로 시작한 Combine 공부(2) 본문

iOS

[Combine] Receiving and Handling Events with Combine - developer 문서로 시작한 Combine 공부(2)

teunteun2 2023. 6. 23. 17:34

이전 포스팅에서 이어집니다

https://teunteun2.tistory.com/35

 

[Combine] 정의 및 구조 - developer 문서로 시작한 Combine 공부(1)

계속 RxSwift만 사용해오다가 Hous- 프로젝트 업데이트 기념,, RxSwift + ReactorKit으로 구현되어있는 코드를 Combine으로 리팩토링 해보고자 Combine 공부를 시작했습니다. 시간도 많아지고 꽤 여유로우니

teunteun2.tistory.com

 

 

 

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

 

Receiving and Handling Events with Combine | Apple Developer Documentation

Customize and receive events from asynchronous sources.

developer.apple.com

해당 Article을 번역하고 정리하였습니다. 

 

Article - Receiving and Handling Events with Combine

비동기 소스로부터 이벤트를 받고, 사용자 정의하는 방법을 정리한 아티클

 

 

OverView

Combine 프레임워크는 앱이 이벤트를 처리하는 방법에 대한 선언적 접근 방식을 제공합니다.

잠재적으로 여러 delegate callback 또는 completion handler closure를 구현하는 대신

주어진 이벤트 소스에 대한 single processing chain을 만들 수 있습니다.

chain의 각 부분은 이전 단계에서 수신한 요소에 대해 고유한 action을 수행하는 Combine operator입니다.

텍스트 필드의 내용을 기준으로 table/collection view 를 필터링해야 하는 앱을 생각해 보십시오.

AppKit에서 텍스트 필드의 각 키 입력은 Combine을 통해 구독할 수 있는 Notification을 생성합니다.

알림을 받은 후 operator를 사용하여 이벤트 전달 내용과 이벤트 전달 시간을 변경하고

최종 결과를 앱의 UI를 업데이트할 수 있습니다.

 

 

Subscriber에 Publisher를 연결

1. Publisher 생성

Combine을 통해 텍스트필들의 notification을 받으려면, NotificationCneter의 default 인스턴스에 접근해야 하며,

default 인스턴스의 publisher 메서드를 호출해야 합니다.

 

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

Publisher을 생성했다면, Publisher로부터 방출된 요소를 받기 위해 Subscriber를 사용합니다.

- subscriber은 방출된 요소를 받기 위해 associated type 인 Input 타입을 정의하며,

- publisher은 방출된 요소를 전달하기 위해 associated type인 output 타입을 정의합니다.

- 또한 subscriber & publisher 모두 자신이 생성하거나 받게 될 에러를 표시하기 위해 Failure 타입을 정의합니다.

 

** associated type ?

> protocol에서 실제 type이 정해지지 않은 generic 역할을 하는 typealias를 말한다.
(예전엔 associated type이 아닌 typealias였음)

 

따라서 subscriber을 publisher와 연결하기 위해선,

Output과 Input이 매치를 이뤄야 하며, Failure 타입 또한 매치 되어야 합니다.

 

 

 

2. Subscribe 생성

Combine은 자동으로 output input 그리고 failure 타입을 매치해주는 두개의 subscibers를 제공합니다.

 

sink(receiveCompletion: , receiveValue: )

-> sink는 두개의 클로저 파라미터를 가지고 있습니다

-> 첫번째 클로저는 publisher가 성공적으로 끝났거나, 에러가 났을 때 실행됩니다.

-> 두번째 클로저는 publisher에서 elements를 받았을 때 실행됩니다.

Ex, Publisher가 완료되었을 때 & element를 받을 때마다 log 찍기
let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)
    .sink(receiveCompletion: { print ($0) },
          receiveValue: { print ($0) })

 

assign(to: , on: )

-> Failure가 절대 일어나지 않을 경우에 사용 가능합니다.

 

 

Publisher로부터 받을 수 있는 element 개수는 무제한입니다.
따라서 element를 받는 비율에 제한을 두고싶다면 직접 subscriber protocol을 채택하여 생성할 수 있습니다.

 

 

Operators로 Output type을 변경하기

sink subscribe에서는 receiveValue 클로저에서 발생된 value값으로 모든 일을 수행해야 했습니다.

이것은 클로저 내에서 받은 element를 바탕으로 가공하거나, 호출 사이에서 상태를 유지해야할 경우

많은 코드를 작성해야 하므로 부담이 될 수 있습니다.

그래서 Combine에서는 Operator를 제공합니다.

 

예를 들어 텍스트필드의 string 값만 필요할 때,

NotificationCenter.Publisher.Output으로 콜백에서 string만 받기엔 편리한 타입이 아닙니다.

 

Publisher의 output은 본질적으로 시간에 따른 element 시퀀스이기 때문에,

Combine은 map(_: ), flatMap(maxPublishers:, _:), reduce(_:, _: )와 같은 시퀀스 수정 연산자를 제공합니다.

 

EX)

Publisher의 output 타입을 변경하고 싶다면, 다른 타입을 리턴할 수 있는 클로저를 가진 map 연산자를 사용할 수 있습니다.

이 경우, NSTextField 타입의 오브젝트를 받아 map을 사용하여 텍스트필드의 string value를 추출할 수 있습니다.

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

또는 NSTextField 오브젝트에서 원하는 stringvalue를 얻어낸 다음, 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)

 

 

Operators로 Publisher를 커스텀화

수동적으로 더 필요한 작업을 연산자를 이용하여 편리하게 publisher를 확장할 수 있습니다.

연산자를 이용해 이벤트처리 체인을 향상시킬 수 있는 세가지 방법입니다.

 

1. TextField 문자열을 ViewModel에서 추가적으로 값을 가공하는 대신,
filter 연산자를 사용해서 '특정 길이의 문자열을 무시하거나, 기호를 거부' 하는 필터링을 할 수 있습니다.

2. 대용량 DB를 쿼리하여 filter의 비용이 클 경우,
debounce(for:, scheduler:, options:)를 사용하여 일정시간 동안 사용자의 타이핑을 막아둘 수 있습니다.
여기서 RunLoop 클래스는 지연시간을 초/밀리초 단위로 편리하게 지정할 수 있도록 해줍니다.

3. Publisher 결과가 UI를 업데이트 할 때,
receive(on:, options:)를 호출해서 메인스레드에 콜백을 보낼 수 있습니다.
RunLoop클래스를 통해 제공되는 scheduler 인스턴스를 첫번째 파라미터에 지정하면,
main RunLoop에서 subscriber을 호출하도록 Combine에게 지시할 수 있습니다.

 

세가지 체이닝을 모두 적용한 예시 코드입니다.

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)

 

 

사임되었을 때 publisher을 취소하는 방법

Publisher은 정상적으로 완료하거나 실패할 때까지 계속 이벤트를 방출합니다.

만약 더이상 Publisher을 구독하고 싶지 않다면, 구독을 취소할 수 있습니다.

Subscriber 타입은 Cancellable 프로토콜이 구현되어 cancel() 기능을 사용할 수 있는 sink(), assign()을 통해 생성됩니다.

Custom Subscriber을 만들 땐 꼭 Cancellable 프로토콜을 구현하여 cancel() 메서드를 사용할 수 있도록 해야합니다.

sub?.cancel()