이전 포스트에서 publisher의 개념과 subscriber에 대한 개념을 알아 보았다.
다시 간략히 정리하자면,
subscriber
는 observer 객체로 이벤트(값)을 수신publisher
는 observable 객체로 이벤트 발생을 감지하고 subscriber로 이벤트(값)를 전달한다.이번 포스트에서는 사용자 정의 이벤트를 생성하고 수신하는 방법에 대하여 알아보고자 한다.
또한 Foundation
framework에 정의 되어 있는 여러 타입 중에서 Timer
, NotificationCenter
, URLSession
등을 통해 publisher를 생성할 수 있는데 이번에는 NotificationCenter
를 통해 알아 보고자 한다.
시작에 앞서 NotificationCenter
class에 대하여 알아보려고 한다.
Apple 문서에는 다음과 같이 설명 되어 있다.
NotificationCenter
A notification dispatch mechanism that enables the broadcast of information to registered observers.
NotificationCenter는 class 타입으로 observer에게 정보를 전달할 수 있도록 notification을 전달하는 매커니즘 정도로 보면 될듯 하다.
NotificationCenter와 Combine을 사용하면 다음과 같이 Publisher를 만들 수 있다.
import Combine
import Foundation
let notification: Notification.Name = Notification.Name("pass Notification")
let publisher: NotificationCenter.Publisher = NotificationCenter.default.publisher(for: notification)
❗️ NotificationCenter와 Notification은 모두 Foundation framework에 정의 되어 있으므로 Foundation을 import 하도록 한다.
먼저 전달할 notification을 식별할 수 있도록 Notification
의 Name
을 통해 NotificationCenter에게 전달할 notification을 정의 하였다.
그리고 NotificationCenter
의 default
의 publisher
프로퍼티로 notification을 등록하면 해당 notification이 NotificationCenter로 들어 왔을때 값을 observer에게 전달하는 publisher가 생성된다.
여기서 publisher와 subscriber 데이터 전달의 핵심은 바로 NotificationCenter의 post
메서드이다.
NofiticationCenter
의 default
의 post
메서드를 호출하면 publisher에게 매개변수로 전달된 notification을 전달한다.
그럼 publisher는 이벤트 발생을 감지하고 observer에게 이벤트(값)을 전달한다.
NotificationCenter.default.post(Notification(name: notification))
하지만 아직 subscriber가 publisher를 subscribe하지 않았기 때문에 post 메서드를 호출해 봤자 아무 일도 일어나지 않는다.
이전 포스트와 마찬가지로 Combine의 sink
메서드를 통해 publisher를 subscribe 해보려고 한다.
let subscribtion: AnyCancellable = publisher.sink(receiveCompletion: {
print("received completion: \($0)")
}, receiveValue: {
print("received value: \($0)")
})
이제 publisher까지 subscribe 했기 때문에 post
메서드를 호출하면 NotificationCenter에 notification을 전달한다. 그럼 publisher가 이벤트 발생을 감지하고 observer에게 이벤트(값)을 전달하게 된다.
NotificationCenter.default.post(Notification(name: notification))
NotificationCenter.default.post(Notification(name: notification))
post
메서드는 한번 호출되면 한번의 notification을 전달하기 때문에 여러번 호출하면 여러번의 notification이 전달되어 다음과 같은 결과나 나온다.
//출력 결과
//received value: name = pass Notification, object = nil, userInfo = nil
//received value: name = pass Notification, object = nil, userInfo = nil
결과 값을 보면 알겠지만 Publisher가 receiveValue
parameter로 값을 전달한 것은 알 수 있으나 receiveCompletion
parameter로는 completion이 전달 되지 않을 것을 알 수 있다.
그 말인 즉, publisher는 프로그램이 종료 되기 전까지 observer에게 값을 전달할 준비를 계속 하고 있기 때문에 더 이상 전달할 값이 없다면 AnyCancellable
타입의 cancle()
메서드를 사용하면 더 이상 publisher가 observer에게 값을 더 이상 전달하지 않도록 한다.
subscribtion.cancel()
당연하게도 cancel() 메서드를 사용하면 이후로는 post 메서드를 호출하여도 아무 일도 일어나지 않는다.
혹은 두번째 방법으로 AnyCancellable 타입의 store
메서드를 사용하는 방법이 있다.
AnyCancellable 타입을 가지는 Set
을 생성하고 store
메서드로 위의 subscrition을 넘겨주면 알아서 subscribe를 해제할 수 있다.
var subsribtionSet = Set<AnyCancellable>() //subscribe 해제를 위한 set
subscribtion.store(in: &subsribtionSet) //inout 매개변수로 전달
먼저 AnyCancellable
타입을 담을 Set
을 정의하고 해당 Set을 AnyCancellable
타입의 store
메서드로 전달하면 된다.
또 다른 방법으로 관찰하고자 하는 프로퍼티를 @Published
attribute를 사용하여 정의하면 해당 프로퍼티를 대상으로 하는 publisher를 생성한다.
@Published attribute는 class의 var 프로퍼티에만 사용 가능하니 주의
import Combine
import Foundation
class Person {
@Published var age: Int
init(_ age: Int) {
self.age = age
}
}
인스턴스를 생성 후 publisher를 가져오기 위해 인스턴스의 프로퍼티를 binding 즉,$ prefix
를 통해 publisher를 가져온다.
이때 프로퍼티가 Int 타입으로 반환되는 publisher의 타입은 Published<Int>.Publisher
var personLee: Person = Person(99)
let publisher: Published<Int>.Publisher = personLee.$age
let subscriber: AnyCancellable = publisher.sink(receiveValue: {
print("value changed: \($0)")
})
personLee.age = 20
프로퍼티의 값을 변경 했을때 결과는 다음과 같다.
//실행 결과
//value changed: 99 //초기값
//value changed: 20
필자는 위의 KVO 방식을 구현하면서 반응형 프로그래밍 방식을 이해하는데 조금 더 도움이 되지 않았나 싶다.
감시하는 프로퍼티를 계속 지켜보고 있다가 값의 변화(이벤트)가 발생 하였을때 이벤트를 감지하고 publisher는 observer에게 값을 전달한다.
다음 포스트에서는 NotificationCenter와 KVO를 활용한 실질적인 예제를 한번 다루어 보려고 한다.