YunDaeHyeon
← Lab

옵저버 패턴과 NotificationCenter

스터디2025.08.28
  • Swift

옵저버 패턴(Observer Pattern)

Observer

Observer

[Design Pattern] 내가 보려고 정리하는 Swift 디자인 패턴 (5) - 옵저버 패턴(Observer Pattern with NotificationCenter)

Observer pattern - Wikipedia

옵저버 패턴은 한 객체에서 일어나는 정보를 다른 특정한 여러 객체들에게 전달할 때 사용하는 디자인 패턴이다.

여기서 발생하는 정보, 일어나는 정보는 어떤 모델이나 타입의 추가, 수정, 삭제와 같은 변경사항들, 타입에서 발생하는 다양한 이벤트들을 의미한다고 생각하면 쉽다.

옵저버 패턴에 대하여 간략하게 개념을 좀 정리해보면 아래와 같다.

  1. 한 객체의 상태가 변경될 때 해당 객체(Object, Subject, Publisher) 가 자신의 상태 변경을 등록한 여러 구독자(Observer) 에게 알린다.
  2. 객체 간의 1:N 관계를 통해 타입의 추가, 수정, 삭제 및 이벤트 발생과 같은 변화를 구독자들에게 브로드캐스팅한다.
  3. 각각의 구독자(Observer) 들은 필요시 Subject에 등록하여(addObserver, subscribe 등) Publisher가 변경 사항을 Notify할 때 자동으로 다양한 메서드들을 활용해 변경 내용을 수신할 수 있다.

백문이 불여일견. 일단 직접 해보자.

직접 해보면서 옵저버 패턴 이해하기

[Design Pattern] 내가 보려고 정리하는 Swift 디자인 패턴 (5) - 옵저버 패턴(Observer Pattern with NotificationCenter)

옵저버 패턴에 대해 쉽게 이해하기 위해서 유튜브라는 예시를 들어보자.

대곰이 라는 유튜브 채널이 있고, 그 채널에 대해 _구독_한 다른 사람들이 대곰이 채널에 새로운 영상이 올라올 때마다 알람을 받고자한다.

그렇다면, 아래와 같이 용어가 정리될 것이다.

Subject, Publisher (주체) = 대곰이 채널
→ 옵저버들이 관심있게 보는 주체이자 객체. (특정한 이벤트가 발생되거나 상태가 변경되는 객체)

Observer, Subsciber (관찰자, 구독자) = 대곰이 채널을 구독한 다른 사람들
→ 관심있는 주제의 상태변화를 관찰하고, 반응하는 객체

attach, addObserve, Subscibe = 구독, 등록의 행위
→ 관찰자가 특정 주체를 관찰하게 되는 것 (Observer 객체 내의 어떤 저장 공간에 포함되는 것)

Notify = 알림 (새로운 영상이 등록되었어요!)
→주체가 상태 변화를 관찰자에게 알려주는 것.

Subject(Publisher) 주체 정의하기 (대곰이 채널)

protocol Subject {
	func addObserver(_ observer: Observer)
	func notifyObserver(_ post: Post)
}

옵저버 패턴은 1:N이 기본이다. 발행자는 1명, 구독자는 N명이 될 수 있기 때문에 자주 사용되는 메서드를 추상화시키자.

  • addObserver(_:) : 대곰이 채널을 구독합니다! (Subject를 Observe)
  • notifyObserver(_:) : 구독자들에게 새로운 영상 업로드 사실을 보냅니다! (Observer 들에게 상태 변화 전송)

그 후 실제 대곰이 채널을 구현해보자

class DaeGom: Subject {
    
    // 구독자(옵저버)들의 리스트
    private var observers = [Observer]()
    
    // 새로운 구독자 추가
    func addObserver(_ observer: Observer) {
        observers.append(observer)
    }
    
    // 유튜브 영상 업로드 시 알람 메서드를 호출하여 업로드 사실 전송
    func addVideo(_ post: Post) {
        notifyObserver(post)
    }
    
    // 보관중인 모든 옵저버들에게 영상 업로드 알림 발송
    func notifyObserver(_ post: Post) {
        observers.forEach {
            $0.update(post: post)
        }
    }
}

// 간단한 Post 모델
struct Post {
    let content: String
}

Observer 정의하기 (구독자들)

// 옵저버들이 사용할 공통 메서드
protocol Observer {
    func update(post: Post)
}

class Subscriber: Observer {
    private let name: String
    
    init(name: String) {
        self.name = name
    }
    
    func update(post: Post) {
        print("\(name)님, 대곰이 채널에 새로운 영상이 업로드 되었어요!")
    }
}

실제 실행

let daegom = DaeGom() // 주체 생성

let user1 = Subscriber(name: "대현") // 구독자 생성
let user2 = Subscriber(name: "길동")

daegom.addObserver(user1) // 구독자 추가
daegom.addObserver(user2) // 구독자 추가

let newVideo = Post(content: "재미있는 옵저버 공부하기")
daegom.addVideo(newVideo) // 발행
// 대현님, 대곰이 채널에 새로운 영상이 업로드 되었어요!
// 길동님, 대곰이 채널에 새로운 영상이 업로드 되었어요!
  • 주체(Subject)가 돨 주체 객체를 생성한다.
  • user1과 user2는 Subject에 대해 알림 설정을 신청하였다. addObserver()
  • Subject에서 새로운 영상이 추가된 순간(addVideo) 연결된 구독자들에게 모두 알람이 전달된다.

다양한 옵저버 패턴들

Swift 디자인패턴 Observer Pattern (옵저버 패턴)

옵서버 패턴

Observation | Apple Developer Documentation

NotificationCenter | Apple Developer Documentation

위에 있는 예시 코드는 옵저버 패턴을 간단하게 설명해주는 코드이다.

Swift 생태계에서는 대표적인 옵저버 패턴 구현체(도구)가 존재한다.

1. Combine (Apple, iOS 13 +)

Combine은 Apple에서 제공하는 옵저버 패턴의 공식적인 프레임워크이다.

  • Publisher → Subscriber 구조
  • 데이터의 흐름을 선언적으로 표현할 수 있다. (map, filter, combineLatest, assign, sink 등 다양한 연산자를 지원한다.)
  • SwiftUI와 매우 긴밀하다. (@Published, @ObservableObject)
import Combine

class ViewModel: ObservableObject {
    @Published var text: String = ""
}

let vm = ViewModel()
var cancellables = Set<AnyCancellable>()

vm.$text
    .sink { newValue in
        print("텍스트 변경됨: \(newValue)")
    }
    .store(in: &cancellables)

vm.text = "Hello, Combine!"
// 출력: 텍스트 변경됨: Hello, Combine!

2. Observation (iOS 17+, Swift 5.9+)

Observation은 Swift에서 가장 최근이 출시된 프레임워크이다.

  • KVO와 Combine의 후속 프레임워크이다.
  • @Observable 기반이다.
  • 단순하고 가볍다!
import Observation

@Observable
class Profile {
    var name: String = "대곰"
}

let profile = Profile()

// SwiftUI View
struct ContentView: View {
    @Bindable var profile: Profile

    var body: some View {
        Text(profile.name) // name 바뀌면 자동 UI 업데이트
    }
}

3. RxSwift (서드파티)

RxSwift는 Combine보다 다양한 기능을 가지고 있다.

  • Observable + Observer 패턴 기반이며 연산자가 300개 넘게 있다..
  • iOS 커뮤니티에서 가장 오래된 인지도와 생태계를 가지고 있다!
  • 대신 러닝 커브가 높고.. 서드파티다 보니 외부 라이브러리에 의존성이 필요하다.
import RxSwift

let disposeBag = DisposeBag()
let subject = PublishSubject<String>()

subject
    .subscribe(onNext: { value in
        print("새로운 값: \(value)")
    })
    .disposed(by: disposeBag)

subject.onNext("Hello RxSwift!")
// 새로운 값: Hello RxSwift!

4. NotificationCenter (Foundation)

NotificationCenter는 Foundation에 내장되어있는 가장 기본적인 옵저버 패턴 구현체 이다.

  • 앱 전역에 구독자들을 대상으로 이벤트 브로드캐스팅이 가능하다.
  • 문자열 기반 키(Notification.name)을 사용한다.
  • 하지만, 문자열 기반 키다보니 타입의 안정성이 낮다..
  • 메모리 관리와 해제 시점 관리에 주의가 필요하다.
  • 작은 앱이나 간단한 이벤트 전달에 유용하다.
let notificationName = Notification.Name("NewPost")

// 구독자
let observer = NotificationCenter.default.addObserver(
    forName: notificationName,
    object: nil,
    queue: .main
) { notification in
    if let content = notification.userInfo?["content"] as? String {
        print("새 영상: \(content)")
    }
}

// 발행자
NotificationCenter.default.post(
    name: notificationName,
    object: nil,
    userInfo: ["content": "새로운 영상이 추가되었어요!"]
)
// 새 영상: 새로운 영상이 추가되었어요!

다양한 옵저버 패턴들을 정리해보면?

  • 간단한 전역 이벤트: NotificationCenter
  • SwiftUI + 최신 프로젝트: Observation
  • iOS 13~ 지원 + MVVM 구조: Combine
  • 복잡한 리액티브/멀티플랫폼 경험: RxSwift

NotificationCenter 사용 예시

한 번 직접 NotificationCenter를 UIKit 기반으로 사용해보자.

시나리오는 아래와 같다.

  1. 사용자가 버튼을 누르면 Notification을 발행한다. (Post)
  2. 다른 VC에서 해당 알람을 받으면 UILabel을 업데이트한다.

1. Notification 정의

우선, 발행자가 구독자에게 어떤 이름으로 변경사항을 보낼 것인지 정의해보자.

extension Notification.Name {
	static let didTapButton = Notification.Name("didTapButton")
}
  • 사용자가 버튼을 클릭하면 구독자에게 didTapButton이라는 이름의 알림을 전송한다.

2. 발행자 정의

// MARK: - 발행자 (Publisher)
class FirstViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        let button = UIButton(type: .system)
        button.setTitle("알림 보내기", for: .normal)
        button.addTarget(self, action: #selector(postNotification), for: .touchUpInside)
        
        button.center = view.center
        button.frame.size = CGSize(width: 200, height: 50)
        view.addSubview(button)
    }
    
    @objc private func postNotification() {
        NotificationCenter.default.post(
            name: .didTapButton,
            object: nil,
            userInfo: ["message": "버튼이 눌렸습니다!"]
        )
    }
}

스토리보드가 아닌 코드 베이스로 간단하게 작성해보았다.

  • 사용자는 버튼을 클릭할 시 postNotification()이 실행되어 구독자들에게 didTapButton이라는 이름으로 message: 버튼이 눌렸습니다! 라는 데이터가 전달된다.\

3. 구독자 정의

class SecondViewController: UIViewController {
    
    private let label = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemYellow
        
        label.text = "아직 알림 없음"
        label.textAlignment = .center
        label.frame = view.bounds
        view.addSubview(label)
        
        // 옵저버 등록
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleNotification(_:)),
            name: .didTapButton,
            object: nil
        )
    }
    
    @objc private func handleNotification(_ notification: Notification) {
        if let message = notification.userInfo?["message"] as? String {
            label.text = message
        }
    }
    
    deinit {
        // 옵저버 해제 (메모리 누수 방지)
        NotificationCenter.default.removeObserver(self)
    }
}

구독자는 viewDidLoad() 시점에 addObserver를 사용하여 발행자를 등록한다.

그 후 등록한 발행자에게 새로운 알람이 수신된다면 selector가 실행되어 handleNotification 이 실행되어 Label을 업데이트한다.