이 글은 ‘애플 개발자 문서’ 에 있는 Receiving and Handling Events with Combine 문서를 번역하고, 설명이 필요한 부분은 주석을 달아서 정리한 것입니다. ‘Combine’ 프레임웍에 대해서는, Combine Framework (프레임웍) 문서를 보도록 합니다.1
‘비동기 소스’ 에서 온 이벤트를 사용자 정의해서 받기
Combine
프레임웍1 은 앱이 이벤트를 가공할 수 있도록 ‘선언형 접근 방식’2 을 제공합니다. 잠재적으로 여러 개의 ‘대리자 콜백’ 이나 ‘처리자 클로저’ 를 구현하기 보다는, 주어진 이벤트 소스를 위한 ‘단일 가공 연쇄’ 을 생성할 수 있습니다. 연쇄의 각 부분은 이전 단계에서 받은 원소에 서로 별개의 행동을 수행하는 Combine
연산자’ 입니다.
‘텍스트 필드’ 의 내용물을 기초로 ‘표’ 나 ‘집합체 뷰’ 를 걸러내야 할 필요가 있는 앱을 고려해 봅니다. AppKit
3 에서, ‘텍스트 필드’ 의 각 ‘키 입력 (keystroke)’ 은 ‘컴바인’ 으로 구독할 수 있는 Notification 을 만들어 냅니다. ‘알림 (notification)’ 을 받은 후, 연산자를 사용하여 이벤트 배달 간격과 내용을 바꿀 수 있으며, 최종 결과를 사용하여 앱의 사용자 인터페이스를 갱신합니다.
Combine
으로 ‘텍스트 필드’ 의 ‘알림’ 을 받으려면, NotificationCenter 의 ‘default
인스턴스’4 에 접근하여 publisher(for:object:) 메소드를 호출합니다. 이 호출은 알림을 받고 싶은 ‘알림 이름’ 과 ‘소스 객체’ 를 취하고, ‘알림 원소’ 를 만드는 ‘발행자’ 를 반환합니다.
let pub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
발행자로부터 이벤트를 받으려면 Subscriber 를 사용합니다. ‘구독자’ 는, 받을 타입을 선언하는, Input 이라는, ‘결합 타입’5 을 정의합니다. ‘발행자’ 도, 만드는 것이 무엇인지를 선언하는, Output 이라는, 타입을 정의합니다. 발행자와 구독자 둘 다, 자신이 만들고 받는 에러 종류를 지시하는, Failure 라는, 타입을 정의합니다. 구독자를 발행자와 연결하려면, Output 이 Input 과 반드시 일치해야 하며, Failure 타입들도 반드시 일치해야 합니다.
Combine
은, 첨부한 발행자의 ‘출력 (outout)’ 및 ‘실패 (failure) 타입’ 과 자동으로 일치하는, 두 ‘내장된 구독자’ 를 제공합니다:
예를 들어, ‘sink’ 구독자를 사용하여 발행자가 완료하고, 매 번 원소를 받을 때마다 기록을 남길 수 있습니다:
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.sink(receiveCompletion: { print ($0) },
receiveValue: { print ($0) })
sink(receiveCompletion:receiveValue:)
와 assign(to:on:)
둘 다 자신의 발행자에 무제한의 원소 개수를 요청하는 구독자 입니다. 원소를 받을 비율을 제어하려면, Subscriber 프로토콜을 구현하여 자신만의 ‘구독자’ 를 생성합니다.
이전 절에 있는 ‘sink’ 구독자는 receiveValue
클로저에서 모든 작업을 수행합니다. 이는 받은 원소로 많은 사용자 정의 작업을 하거나 불러냄 사이에 상태를 상태를 유지할 필요가 있는 경우 짐이 될 수 있습니다. Combine
의 장점은 ‘이벤트 배달’ 을 사용자 정의하기 위해 연산자를 조합하는 것에서 비롯합니다.
예를 들어, NotificationCenter.Publisher.Output 는 ‘텍스트 필드’ 의 문자열 값만 필요한 경우에 콜백에서 받기 편리한 타입은 아닙니다. 발행자의 출력은 본질적으로 시간에 따른 일련의 원소들이므로, Combine
은 [map(:)](https://developer.apple.com/documentation/combine/publisher/map(:)-99evh), [flatMap(maxPublishers::)](https://developer.apple.com/documentation/combine/publisher/flatmap(maxpublishers::)-3k7z5), 그리고 reduce(::) 같은 ‘시퀀스-수정 연산자’ 를 제안합니다. 이 연산자들의 작동 방식은 스위프트 표준 라이브러리에 있는 이들의 ‘동치 연산자 (equivalents)’ 들과 비슷합니다.
발행자의 출력 타입을 바꾸려면, 다른 타입을 반환하는 클로저를 담은 [map(:)](https://developer.apple.com/documentation/combine/publisher/map(:)-99evh) 연산자를 추가합니다. 이 경우, 알림 객체를 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:)
로 대체합니다. 다음 예제는 ‘발행자 연쇄’ 로부터 받은 문자열을 취해서 이를 사용자 정의 ‘뷰 모델 객체’6 의 filterString
에 할당합니다:
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.map( { ($0.object as! NSTextField).stringValue } )
.assign(to: \MyViewModel.filterString, on: myViewModel)
다른 경우라면 수동으로 코딩할 필요가 있었을 행동을 연산자를 사용하여 수행하도록 Publisher 인스턴스를 확장할 수 있습니다. 다음은 연산자를 사용하여 이 ‘이벤트-가공 망’ 을 개선할 수 있는 세 가지 방식입니다:
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)
‘발행자’ 는 정상적으로 완료하거나 실패할 때까지 원소를 계속 내보냅니다. 더 이상 ‘발행자’ 를 구독하고 싶지 않으면, ‘구독’ 을 취소할 수 있습니다. sink(receiveCompletion:receiveValue:) 와 assign(to:on:) 로 생성한 ‘구독자’ 둘 다, cancel() 메소드를 제공하는, Cancellable 프로토콜을 구현합니다:
sub?.cancel()
사용자 정의 Subscriber 를 생성하는 경우, 처음 구독할 때 ‘발행자’ 가 Subscription 객체를 보냅니다. 이 ‘구독 (subscription)’ 을 저장한 다음, ‘발행’ 을 취소하고 싶을 때 이것의 cancel() 메소드를 호출합니다. 사용자 정의 ‘구독자’ 를 생성할 때는, Cancellable 프로토콜을 구현해서, 자신의 cancel() 구현이 저장된 ‘구독’ 쪽으로 향하게 해야 합니다.
‘선언형 (declarative)’ 에 대한 더 자세한 정보는, 위키피디아의 Declarative programming 항목과 선언형 프로그래밍 항목을 보도록 합니다. ↩
AppKit
은 ‘macOS’ 의 ‘UI’ 를 구성하기 위한 프레임웍입니다. ‘앱 킷 (AppKit)’ 에 대한 더 자세한 정보는, 애플 개발자 문서의 AppKit 을 보도록 합니다. ↩
‘default
인스턴스’ 는 NotificationCenter
에 정의되어 있는 ‘전역 변수’ 입니다. ↩
‘결합 타입 (associated type)’ 은 프로토콜에서 사용하는 타입에 ‘자리 표시용 (placeholder) 이름’ 을 부여한 것입니다. ‘결합 타입’ 에 대한 더 자세한 정보는, 스위프트 프로그래밍 언어 (Swift Programming Language) 책의 Generics (일반화) 장에 있는 Associated Types (결합 타입) 부분을 보도록 합니다. ↩
여기서의 ‘뷰 모델 객체 (view model object)’ 는 ‘MVVM’ 에 있는 ‘뷰 모델 (View Model)’ 을 말하는 것입니다. 스위프트에서 ‘MVVM’ 의 ‘뷰 모델’ 은 항상 ‘클래스’ 로 구현하기 때문에 ‘뷰 모델 객체’ 라는 용어를 사용한 것으로 추측됩니다. ↩