NotificationCenter — 본질, 작동 원리 및 알림 아키텍처

저자: IT Sectr 게시일: 2026-03-18 읽는 시간: 10 분

NotificationCenter는 발신자와 수신자 간의 직접적인 연결 없이 애플리케이션 구성 요소 간에 알림을 보내고 받기 위한 iOS의 시스템 메커니즘입니다. Observer 패턴을 기반으로, NotificationCenter는 객체가 이벤트를 구독하고 비동기적으로 응답할 수 있도록 합니다. Apple Documentation(2025)에 따르면, NSNotificationCenter는 post(name:object:)를 통한 동기 알림 전송과 NotificationQueue를 통한 지연 전송을 모두 지원합니다. 알림 센터는 단일 프로세스 내에서 작동하며 애플리케이션 경계를 넘지 않습니다.

핵심 요점

  • NotificationCenter — iOS 구성 요소 간 이벤트 교환을 위한 Observer 패턴의 구현입니다.
  • addObserver는 특정 이름과 발신자 객체로 알림에 객체를 구독시킵니다.
  • post(name:object:)는 구독 중인 모든 옵저버에게 동기적으로 알림을 보냅니다.
  • removeObserver는 deinit에서 호출해야 하며, 그렇지 않으면 알림 전송 시 충돌이 발생합니다.
  • NotificationQueue는 비동기 전달을 위해 알림을 지연시킬 수 있습니다.

NotificationCenter란?

NotificationCenter(NSNotificationCenter)는 객체 간 느슨하게 결합된 통신을 구현하기 위한 내장 iOS 메커니즘입니다. Observer 패턴을 통해 하나의 객체(발신자)가 직접적인 참조 없이 여러 다른 객체(옵저버)에게 이벤트를 알릴 수 있습니다. NotificationCenter는 세 가지 엔터티로 작동합니다: Notification.Name(알림 식별자), Notification(데이터가 있는 컨테이너), NotificationCenter(디스패처). 각 애플리케이션에는 공유 default center가 있습니다.

NSNotification 및 Notification.Name

Notification.Name은 알림 유형을 식별하는 구조체입니다. extension Name: Notification.Name(“MyNotification”)을 통해 생성됩니다. Notification은 name, object(발신자), userInfo(데이터가 있는 딕셔너리)를 포함하는 객체입니다. 시스템 알림은 상수로 선언됩니다: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. 사용자 정의 알림은 이름 충돌을 피하기 위해 extension을 통해 그룹화해야 합니다. 이름은 역도메인 형식이어야 합니다.

swift
// 사용자 정의 알림 정의
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// 데이터와 함께 알림 보내기
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

옵저버 추가하기 (addObserver)

옵저버는 addObserver(_:selector:name:object:) 메서드를 통해 알림을 구독합니다. Selector는 알림 수신 시 호출될 메서드입니다. object 매개변수는 특정 발신자의 알림을 필터링할 수 있습니다. object가 nil이면 옵저버는 모든 발신자로부터 지정된 이름의 모든 알림을 수신합니다. iOS 9부터 addObserver는 block-based API에서 수동 제거가 필요하지 않지만, selector-based는 여전히 removeObserver가 필요합니다.

swift
// 알림 구독 (selector 기반)
NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleDataUpdate),
    name: .dataDidUpdate,
    object: nil
)

@objc func handleDataUpdate(_ notification: Notification) {
    guard let userId = notification.userInfo?["userId"] as? Int else { return }
    updateUI(for: userId)
}

// 알림 구독 (block 기반, iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
    forName: .dataDidUpdate,
    object: nil,
    queue: .main
) { [weak self] notification in
    guard let self else { return }
    self.handleNotification(notification)
}

NotificationCenter의 작동 방식

NotificationCenter는 매핑 테이블(이름 → 옵저버 집합)을 저장합니다. 발신자가 post(name:object:)를 호출하면, 알림 센터는 동기적으로 해당 이름을 구독한 모든 옵저버를 순회하며 해당 selector 또는 블록을 호출합니다. 핵심 특징: post는 모든 핸들러가 완료될 때까지 현재 스레드를 차단합니다. 핸들러가 무거운 작업을 수행하면 발신자가 지연됩니다. NotificationQueue는 알림 전달을 지연시켜 이 문제를 해결합니다.

동기 전송 (post)

post(name:object:userInfo:) 메서드는 즉시 모든 옵저버에게 알림을 보냅니다. 호출은 동기식입니다 — post 이후의 코드는 모든 핸들러가 완료된 후에만 실행됩니다. 옵저버 호출 순서는 보장되지 않으며 실행 간에 변경될 수 있습니다. 순차 처리를 위해 coalescing과 함께 NotificationQueue를 사용하세요. 동일한 알림의 핸들러 내에서 post를 호출하지 마세요 — 무한 재귀가 발생합니다.

지연 전송 (NotificationQueue)

NotificationQueue는 비동기 전달을 위해 알림을 큐에 추가합니다. coalescing(동일 알림 병합)과 전달 큐 선택(asap, idle, modal)을 지원합니다. Coalescing은 마지막 값만으로 알림하면 되는 빈번한 이벤트(다운로드 진행률)에 유용합니다. NotificationQueue는 실행 루프를 사용하여 발동하므로 활성 실행 루프가 있는 스레드에서만 작동합니다.

swift
// NotificationQueue를 통한 지연 전송
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// Coalescing: 여러 알림이 하나로 병합됨
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

// DispatchQueue를 통한 비동기 전달
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}

Notification vs Delegate vs KVO

iOS는 객체 간 통신을 위한 세 가지 주요 메커니즘을 제공합니다: NotificationCenter, Delegate, KVO(Key-Value Observing). 각각은 알림 문제를 해결하지만 결합도, 성능 및 타입 안전성에서 서로 다른 트레이드오프가 있습니다. 메커니즘 선택은 일대일 또는 일대다 관계와 데이터 전송의 필요성에 따라 달라집니다.

특성NotificationCenterDelegateKVO
결합도약함 (알림 이름)강함 (프로토콜)중간 (키)
관계일대다일대일일대다
타입 안전성낮음 (userInfo가 Dictionary)높음 (프로토콜 메서드)중간 (Any?)
성능중간 (테이블 순회)높음 (직접 호출)낮음 (NSObject)
비동기성동기 (post가 차단)발신자 스레드에서 동기변경 시 동기

NotificationCenter를 선택해야 하는 경우

NotificationCenter는 여러 독립적인 구성 요소가 응답해야 하는 이벤트에 이상적입니다. : 애플리케이션 설정 변경, 사용자 로그아웃, 백그라운드에서 푸시 알림 수신. NotificationCenter는 느슨하게 결합된 모듈(기능 A가 기능 B를 알 필요가 없는 경우)에도 적합합니다. 단점은 타입 안전성 부족입니다: userInfo 키는 열거형이 아닌 문자열입니다.

Delegate 또는 KVO를 선택해야 하는 경우

Delegate는 명확한 계약(tableView.delegate)이 있는 일대일 관계에 적합합니다. Delegate는 더 빠르고 타입별로 안전합니다. KVO는 모델의 특정 속성(isLoading, progress) 변경을 관찰하는 경우에 적합합니다. KVO는 NSObject 상속이 필요하며 디버깅이 어려울 수 있습니다(매직 스트링 키). 최신 Swift에서는 Combine과 async 시퀀스가 세 가지 접근 방식을 모두 대체합니다.

AddObserver: 동기 및 비동기 알림

addObserver 메서드는 두 가지 구독 방식을 지원합니다: selector-based(전통적)와 block-based(클로저 사용). Selector-based는 @objc 호환성과 수동 옵저버 제거가 필요합니다. Block-based(iOS 9+)는 캡처 목록을 사용할 수 있으며 강한 참조 없이 블록을 사용할 때 OS에 의해 자동으로 관리됩니다. Block-based는 큐도 지원합니다 — 옵저버는 지정된 큐에서 알림을 수신합니다.

Selector 기반 addObserver

Selector를 통한 전통적인 구독 방식입니다. 핸들러 메서드는 @objc로 표시되어야 하며 선택적 Notification을 받아야 합니다. 장점: 레거시 Objective-C를 포함한 모든 클래스에서 사용할 수 있습니다. 단점: selector의 타입 안전성 부족, selector 이름의 오타 위험, deinit에서 필수적인 removeObserver. 객체보다 먼저 옵저버가 제거되면 핸들러가 호출되지 않습니다.

블록 기반 addObserver

Block-based API는 알림 수신 시 실행되는 클로저를 받습니다. queue 매개변수는 블록이 실행될 큐를 결정합니다 — UI 업데이트를 위한 메인 큐 또는 데이터 처리를 위한 백그라운드 큐. 반환 값 NSObjectProtocol은 옵저버 제거에 사용됩니다: NotificationCenter.default.removeObserver(observer). Block-based는 최신 Swift에서 선호됩니다.

swift
protocol NotificationToken {
    func dispose()
}

extension NotificationCenter {
    func observe(
        name: NSNotification.Name,
        object: Any? = nil,
        queue: OperationQueue? = .main,
        using block: @escaping (Notification) -> Void
    ) -> NotificationToken {
        let observer = addObserver(forName: name, object: object,
                                   queue: queue, using: block)
        return NotificationTokenWrapper(observer: observer, center: self)
    }
}

// 자동 제거와 함께 사용
class ViewModel {
    private var tokens: [NotificationToken] = []

    func startObserving() {
        let token = NotificationCenter.default.observe(
            name: .dataDidUpdate,
            queue: .main
        ) { [weak self] notification in
            self?.handleUpdate(notification)
        }
        tokens.append(token)
    }

    deinit {
        tokens.forEach { $0.dispose() }
    }
}

메모리 관리 및 옵저버 제거

메모리 누수는 NotificationCenter 작업 시 주요 문제 중 하나입니다. 할당 해제 전에 옵저버가 제거되지 않으면, 알림 전송 시 센터가 이미 할당 해제된 객체의 메서드를 호출하려고 시도하여 EXC_BAD_ACCESS가 발생합니다. iOS 9부터 block-based addObserver는 약한 참조를 사용하지만, selector-based는 여전히 수동 removeObserver가 필요합니다. 모범 사례: deinit에서 옵저버를 제거하세요.

removeObserver를 호출해야 하는 경우

Selector-based: 항상 deinit에서 NotificationCenter.default.removeObserver(self)를 호출하세요. 옵저버가 여러 알림을 구독한 경우 한 번에 모두(매개변수 없이) 또는 이름으로 특정 알림을 제거할 수 있습니다. Block-based: addObserver에서 반환된 토큰으로 removeObserver를 통해 제거하세요. iOS 9+의 block-based에서는 누수가 발생하지 않지만, 성능을 위해 제거를 권장합니다: 할당 해제된 옵저버는 post 중에 순회되지 않습니다.

swift
class SafeObserver {
    private var observers: [NSObjectProtocol] = []

    func addSubscriptions() {
        let token1 = NotificationCenter.default.addObserver(
            forName: .dataDidUpdate, object: nil,
            queue: .main) { [weak self] _ in
            self?.refreshData()
        }
        let token2 = NotificationCenter.default.addObserver(
            forName: .userLoggedOut, object: nil,
            queue: .main) { [weak self] _ in
            self?.logout()
        }
        observers.append(contentsOf: [token1, token2])
    }

    deinit {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
    }

    private func refreshData() { }
    private func logout() { }
}

토큰 패턴을 통한 약한 참조

토큰 패턴은 옵저버 관리를 자동화합니다. 구독 시 토큰 객체(NSObjectProtocol)가 반환되며, 할당 해제 시 자동으로 옵저버를 제거합니다. NotificationTokenWrapper는 NotificationCenter와 옵저버 토큰에 대한 약한 참조를 저장하고 deinit에서 removeObserver를 호출합니다. 이는 AnyCancellable이 구독 수명 주기를 관리하는 Combine 접근 방식에 NotificationCenter를 더 가깝게 만듭니다.

멀티스레드 환경에서의 NotificationCenter

스레드 안전성: NotificationCenter는 post가 모든 스레드에서 호출될 수 있으며, 모든 옵저버가 post가 호출된 동일한 스레드에서 알림을 수신함을 보장합니다. 이는 멀티스레드 애플리케이션에 중요합니다: 백그라운드 스레드에서 알림이 전송되면 핸들러도 백그라운드 스레드에서 실행됩니다. UI 업데이트의 경우 DispatchQueue.main.async를 통해 메인 큐로 처리를 디스패치하세요.

post와 addObserver의 스레드 안전성

NotificationCenter는 다른 스레드에서의 post 및 addObserver 호출에 대해 스레드 안전합니다. 내부 동기화는 잠금을 사용하므로, 여러 스레드에서의 빈번한 post는 경합을 유발할 수 있습니다. 높은 부하 시나리오(1000개 파일의 다운로드 진행률)에서는 별도의 알림 큐 또는 Combine 퍼블리셔를 사용하세요. postingStyle .now의 NotificationQueue는 직접 post와 동일합니다.

Combine을 통한 비동기 전달

NotificationCenter는 NotificationCenter.default.publisher(for:object:)를 통해 Combine 퍼블리셔를 지원합니다. Publisher는 각 알림을 map, filter, debounce, throttle을 통해 변환할 수 있는 Combine 이벤트로 변환합니다. 이는 동기 전달 문제를 해결합니다: Combine은 지정된 Scheduler에서 비동기적으로 알림을 처리합니다. NotificationCenter.publisher는 레거시 메커니즘과 최신 리액티브 프로그래밍 사이의 브리지입니다.

swift
import Combine

class ReactiveViewModel {
    private var cancellables = Set<AnyCancellable>()

    func setupCombineSubscription() {
        NotificationCenter.default
            .publisher(for: .dataDidUpdate)
            .receive(on: DispatchQueue.main)
            .compactMap { $0.userInfo?["progress"] as? Float }
            .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
            .sink { [weak self] progress in
                self?.progressLabel.text = "\(Int(progress * 100))%"
            }
            .store(in: &cancellables)
    }
}

자주 묻는 질문

NotificationCenter는 스레드 안전한가요?

, NotificationCenter는 모든 스레드에서의 post 및 addObserver 호출에 대해 스레드 안전합니다. 하지만 핸들러는 post가 호출된 동일한 스레드에서 실행됩니다. UI 업데이트의 경우 block-based addObserver에서 queue: .main을 사용하거나 핸들러 내에서 DispatchQueue.main.async를 사용하세요. receive(on:)과 함께 Combine 퍼블리셔도 스레드 문제를 해결합니다.

옵저버를 제거하지 않으면 어떻게 되나요?

Selector-based: 옵저버 할당 해제 후 알림 전송 시 EXC_BAD_ACCESS 충돌. Block-based(iOS 9+): 약한 참조 덕분에 누수는 없지만, 알림 센터는 명시적 removeObserver까지 블록을 메모리에 유지합니다. 항상 deinit에서 옵저버를 제거하거나 자동 관리를 위해 토큰 패턴을 사용하는 것이 좋습니다.

NotificationCenter와 KVO의 차이점은 무엇인가요?

NotificationCenter는 관련 없는 구성 요소 간의 임의 이벤트를 위한 브로드캐스트 메커니즘입니다. KVO는 특정 객체의 특정 속성 변경을 관찰합니다. KVO는 NSObject 상속이 필요하며 setter를 통한 속성 변경 시 자동으로 알립니다. NotificationCenter는 post가 명시적으로 호출될 때만 알립니다. 모델 관찰에는 KVO 또는 Combine이 선호됩니다.

애플리케이션에는 몇 개의 NotificationCenter가 존재하나요?

애플리케이션 프로세스당 하나의 default center. NotificationCenter()를 통해 추가 센터를 만들 수 있지만, 실제로는 공유 default가 사용됩니다. 각 센터는 독립적으로 작동합니다 — 하나에서의 post는 다른 센터의 옵저버에게 전달되지 않습니다. 모듈 격리를 위해 역도메인 알림 이름을 통한 별도의 Name 네임스페이스를 사용하세요.

Combine이 NotificationCenter를 대체하나요?

부분적으로. Combine은 NotificationCenter.Publisher를 제공하여 NotificationCenter를 리액티브 스트림으로 래핑합니다. Combine은 동기성 문제(receive(on:)을 통해)를 해결하고, 변환 연산자와 자동 구독 관리(AnyCancellable)를 추가합니다. 하지만 iOS 시스템 알림(UIApplication, UIKeyboard) 및 레거시 코드를 위해 NotificationCenter는 계속 사용됩니다. Combine은 개선이지 대체가 아닙니다.

요약

  • NotificationCenter — iOS에서 느슨하게 결합된 일대다 통신을 위한 Observer 패턴 구현.
  • post는 현재 스레드의 모든 옵저버에게 동기적으로 알림을 보내고 발신자를 차단합니다.
  • addObserver는 selector-based(@objc 사용) 및 block-based(캡처 목록 및 큐 사용) 구독을 지원합니다.
  • removeObserver는 selector-based 구독의 경우 deinit에서 필수이며, 그렇지 않으면 충돌이 발생합니다.
  • NotificationQueue는 빈번한 이벤트에 대해 coalescing을 통한 지연 전달을 제공합니다.
  • 스레드 안전성은 모든 스레드에서의 작동을 보장하지만, 핸들러는 발신자의 스레드에서 실행됩니다.
  • 안전하고 현대적인 구독 관리를 위해 토큰 패턴 또는 Combine 퍼블리셔를 사용하세요.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기