Swift: NotificationCenter와 사용법

Heechan
HcleeDev
Published in
10 min readMar 31, 2022

--

Photo by Prateek Katyal on Unsplash

이번엔 Combine에 대해 작성할까 싶어 문서를 살펴보다가, 뭔가 익숙한 이름의 NotificationCenter가 보였다. 원래는 푸시 알림과 관련한 무언가인줄 알았는데 지금보니 그런게 아닌 것 같다.

이번주는 NotificationCenter와 이와 관련된 비동기 처리에 대해 간단히만 알아보자.

NotificationCenter와 그 사용법

일단 Notification이란 무엇일까? 여기서 Notification은 유저에게 무언가 알려주는 역할이 아닌, 애플리케이션 내에서 정보를 전하기 위해 ‘알림을 방송한다’는 역할을 가지고 있다.

하지만 이 알림, Notification은 그냥 만들어져서 우리가 원하는 곳으로 알아서 가주는 자율주행 시스템은 아니다. 아무래도 우리는 어디로 송신하고 싶은건지, 방송을 듣고 싶다면 어느 채널로 맞춰야 할지도 정해줘야 할 것이다.

그 Notification을 전달하는 중심 역할을 하는 곳이 NotificationCenter다.

무려 iOS 2.0부터 있었던 유서 깊은 클래스다.

공식문서에서는 등록된 ‘Observers’에게 정보의 ‘Broadcast’를 가능하게 하는 ‘Notification Dispatch’ 매커니즘이라고 소개하고 있다.

Notification Dispatch부터 알아보자. 일단 Dispatch를 iOS 개발자라면 DispatchQueue를 사용하면서 들어봤을 것이다. Web 개발자라면 Flux 패턴에 대해 배우며 Dispatcher에 대해 들어봤을 것이다.

여기서 이 Dispatch는 어떤걸 전송하는 역할을 담고 있는 것을 의미한다. 예를 들면 DispatchQueue는 스레드에 어떠한 작업을 해달라고 보낼 것들의 Queue고, Flux 패턴의 Dispatcher는 원하는 액션을 처리하기 위한 Reducer에게 그 액션을 보내는 역할을 한다.

Thread에 Task를 보내는 DispatchQueue — 출처

근데 그냥 알아서 각자가 보내면 될텐데 왜 이렇게 Dispatch를 사용하는걸까? 이렇게 하나의 Dispatcher를 사용하면 데이터의 흐름을 한 점을 지나게 할 수 있다는 장점이 있다. 특히 DispatchQueue, NotificationCenter 같은 녀석들은 우리가 직접 접근할 수 없는 시스템 적인 기능을 이용하기 위해 도와주는 녀석들이다. 따라서 관련 기능을 이미 구현해 가지고 있는 하나의 Dispatcher를 이용하는 것이 안전하다.

그리고 Broadcast와 Observers에서도 알 수 있는 점이 있다.

어떤 곳에서 이벤트가 일어났다, 조건이 충족되었다는 점을 다른 View, 혹은 ViewController에도 알리고 싶으면 Notification을 Broadcast해야 한다. 위에서 말한 것처럼 NotificationCenter는 Dispatcher의 역할을 하기 위해, 우리가 소식을 알려주면 앱 내부에서 그걸 방송해주는 역할을 할 것이다.

그런데 생각해보면, 방송만 한다고 되는건 아니다. 라디오도 전파는 쏘지만 의미가 있으려면 그 주파수를 잡는 청자가 있어야 한다.

이 NotificationCenter에서 뿌려주는 정보도 Observer, 관찰자가 있어야 한다. 또한, 주파수를 맞추는 것마냥, 어떤 정보에 대해 관찰을 할 것인지도 정해야 한다. 많고 다양한 소식이 NotificationCenter로부터 쏟아져나올텐데, 이거에 다 하나하나 반응해줄 수는 없다.

따라서 이 NotificationCenter에는 특정 방송을 관찰, 구독할 수 있도록 돕는 메서드도 포함되어있다.

이렇게 Notification의 전달은, 알림을 만들어내는 Publisher, 이걸 전달하는 Dispatcher역할의 NotificationCenter, 그리고 그 알림을 관찰하는 Observer(혹은 Subscriber)의 관계를 통해 진행된다.

그러면 실제 코드에서의 쓰임을 봐보자.

NotificationCenter.default. ...

싱글톤 패턴에서 흔히 사용하는 것처럼, 앱 전체적으로 하나의 객체에서 관리할 수 있도록 default 가 정의되어있다. 앱의 기본 NotificationCenter로 설정되어있으며, 굳이 개발자가 만들어주지 않아도 앱마다 하나씩은 돌아가고 있다 . 대부분의 작업은 이 default 에 접근해 메서드를 호출하는 식으로 이용할 것이다.

하지만 엄격한 싱글톤은 아니라서 자신의 필요에 따라 NotificationCenter를 더 정의해 사용할 수 있긴 하다. 따라서 특정 범위 내에서만 알림을 주고받고자 할 때는 직접 정의해서 이용할 수도 있을 것이다.

그러면 어떤 작업이 끝났을 때 알림을 보내는 경우를 생각해보자.

NotificationCenter.default.post(name: Notification.Name(name: "workCompleted"), object: nil)

default 에 접근해서 post 메서드를 이용해 “workCompleted”라는 이름을 가진 Notification을 하나 생성해 넘긴다. object를 이용하면 데이터도 우리가 원하는 데이터도 넘길 수 있을 것이다.

위에서 말한 것처럼 받는 쪽과 보내는 쪽이 서로 맞춰야 하기 때문에 Notification.Name을 통해 이름을 맞춘다.

NotificationCenter.default.addObserver(forName: Notification.Name("workCompleted"), object: nil, queue: nil) { _ in
// Handler ...
print("Work Completed!")
}

이를 받고자 할 때는 위처럼 addObserver 를 이용하면 된다. forName 에서는 우리가 받고자 하는 이름에 맞춰서 설정해준다.

그런데 가끔 우리가 설정하지 않아도 되는 알림들이 있다. 유저가 화면 캡처하는게 감지되었다든가, 휴대폰이 기울었다든가… 이런 경우 우리가 Notification.Name을 사용해 이름을 맞춰주는 것이 아니라 이미 약속된 알림 정보를 가져와 사용하면 된다.

... forName: UIDevice.orientationDidChangeNotification, ...

이런 식으로 사용할 수 있다.

위의 예시는 핸들러에서 argument를 _로 해두었다. 하지만 만약 post 에서 어떤 값을 함께 보냈다고 생각해보자. 이 핸들러를 조금 수정할 필요가 있다. 이 핸들러의 타입은 (Notification) -> Void 라서, Notification 객체가 들어온다고 생각하면 된다.

NotificationCenter.default.addObserver(forName: Notification.Name("workCompleted"), object: nil, queue: nil) { notification in
let str = notification.object as! String
// ...
}

만약 보낸 데이터가 String일 경우는 위처럼 Notification 객체의 object 를 String으로 캐스팅해서 사용할 수 있을 것이다.

이외에도 많은 메서드들과 다양한 활용법이 있겠지만, 최소한의 데이터 흐름이 post , addObserver 로 구성될 수 있음을 확인할 수 있다.

Combine에서 NotificationCenter 이용하기

Combine은 애플에서 제공하는 비동기 작업을 처리할 수 있게 해주는 프레임워크다. 약간 많이들 사용하는 RxSwift랑 유사한데, 우리 프로덕트에서는 비동기 처리를 위해 Combine을 사용하고 있다.

이 Combine은 데이터가 업데이트되면 이를 지켜보고 있는 존재에게 알리는 방식으로 구성되어있다. 이를 Publisher와 Subscriber의 관계라고 한다. Publisher는 값이 변화되었다는 사실을 만들어내고, 그 소식은 Chain을 따라 Subscriber에게 전해진다.

그런데, Combine을 사용하려고 할 때 조금 신경 쓰일 수 있는 부분이 있다. 많은 라이브러리나 프레임워크에서 비동기 처리를 위해 NotificationCenter를 이용하고 있는 경우가 많다.

Combine을 사용하고 있는 입장에서는 비동기 작업을 최대한 Combine을 통하도록 하고 싶은데, NotificationCenter로도 관리하는 방식도 유지하기는 조금 열받을 수 있다.

따라서 애플에서는 NotificationCenter의 알림을 Combine의 Publisher로 전환할 수 있는 메서드를 제공한다.

이는 알림을 받는 쪽에서 addObserver 같은 메서드를 사용하지 않고, Combine의 Publisher — Subscriber Chain위에 올릴 수 있도록 하는 메서드다.

예를 들어 뭔가 화면이 기울어졌을 경우 알림이 올텐데, 그때 Publisher로 변경하는 코드를 보자.

NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)

이 코드는, default 에 있는 UIDevie.orientationDidChangeNotification 알림이 post 되면, 그에 따라 알림을 Publish하게 하는 Publisher를 만든 코드다. 여기 for 에 들어가는 것은 Notification.Name("workCompleted") 같이 우리가 설정한 것도 가능하다.

이렇게 Publisher를 만들고 나면, 이를 Subscriber가 sink 하도록 해야 한다.

var cancellable: Cancellable?cancellable = NotificationCenter.default
.publisher(for: UIDevice.orientationDidChangeNotification)
.sink() { _ in print("화면 기울어짐!") }

이 경우, .publisher 메서드로 만들어낸 Publisher를, 바로 Combine의 .sink 를 통해 Subscribe한 것이다. 그리고 이를 cancellable 에 저장했다.

이렇게 하면 NotificationCenter의 default 에서 나오는 알림도 Combine에서 비동기적으로 처리하는 것이 가능해진다.

결론

NotificationCenter가 아마 과거에는 꽤 활용도가 높았을 것 같다. 이거 말고 Key-Value Observing 같은 것도 활용도가 높았던 것 같긴 하다만…

그래도 요즘에는 비동기 처리를 하기 위한 다양한 방법들이 나와 직접 구현해야 하는 경우 그쪽을 사용하긴 하겠지만, 시스템으로부터 알림을 받을 때는 반드시 필요한 내용이니 알아두면 좋을 것 같다.

참고한 것

--

--

Heechan
HcleeDev

Junior iOS Developer / Front Web Developer, major in Computer Science