Swift
NSNotification
Observer Pattern
iOS Development
Programming Tips

Where to remove observer for NSNotification in Swift?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Notification observer cleanup in Swift depends on observer lifetime, not a single universal method call location. Removing too early misses events, and removing too late causes duplicate callbacks or retained objects. The right strategy is choosing lifecycle scope first, then matching add and remove points consistently.

Decide Observer Lifetime Before Coding

Ask this first: how long should this object observe events.

Common lifetimes:

  • Whole object lifetime, such as service objects and long-lived controllers.
  • Visible-screen lifetime, such as view-only refresh events.
  • Temporary task lifetime, such as one-shot operation listeners.

Observer removal location follows this decision.

Selector-Based Observer for Object Lifetime

For selector API observers intended to live with object, add in setup and remove in deinit.

swift
1import UIKit
2
3final class PlayerViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6        NotificationCenter.default.addObserver(
7            self,
8            selector: #selector(handleForeground),
9            name: UIApplication.willEnterForegroundNotification,
10            object: nil
11        )
12    }
13
14    @objc private func handleForeground() {
15        print("foreground event")
16    }
17
18    deinit {
19        NotificationCenter.default.removeObserver(self)
20    }
21}

This keeps observer lifetime aligned with object lifetime.

Visible-Screen Scope Pattern

If callbacks are relevant only while screen is visible, pair add and remove in view lifecycle.

swift
1override func viewWillAppear(_ animated: Bool) {
2    super.viewWillAppear(animated)
3    NotificationCenter.default.addObserver(
4        self,
5        selector: #selector(handleRefresh),
6        name: Notification.Name("RefreshEvent"),
7        object: nil
8    )
9}
10
11override func viewWillDisappear(_ animated: Bool) {
12    super.viewWillDisappear(animated)
13    NotificationCenter.default.removeObserver(
14        self,
15        name: Notification.Name("RefreshEvent"),
16        object: nil
17    )
18}

This prevents off-screen views from processing UI updates.

Block-Based Observer Requires Token Removal

Closure-based API returns a token object. Remove by token, not by self.

swift
1import Foundation
2
3final class SyncWatcher {
4    private var token: NSObjectProtocol?
5
6    init() {
7        token = NotificationCenter.default.addObserver(
8            forName: Notification.Name("SyncFinished"),
9            object: nil,
10            queue: .main
11        ) { [weak self] note in
12            self?.handle(note)
13        }
14    }
15
16    private func handle(_ note: Notification) {
17        print("sync done", note.name.rawValue)
18    }
19
20    deinit {
21        if let token {
22            NotificationCenter.default.removeObserver(token)
23        }
24    }
25}

Forgetting token removal can keep callbacks alive unexpectedly.

Avoid Duplicate Registration

A common bug is adding observers repeatedly in lifecycle methods without symmetric removal. Symptoms include callback firing multiple times per event.

Prevention options:

  • Strict add-remove symmetry.
  • Registration flag for idempotent setup.
  • Centralized observer setup in one method.

Structured lifecycle design prevents this class of bugs.

Threading and Capture Safety

Notifications may be delivered on posting thread. If callback updates UI, ensure execution on main thread.

In block observers, capture self weakly to avoid retain cycles in long-lived notifications.

swift
DispatchQueue.main.async {
    // UI update
}

Correct thread and capture handling matters as much as remove timing.

Targeted Removal Versus Global Removal

If object listens to several notifications, targeted removal is safer than removing all observers blindly.

swift
1NotificationCenter.default.removeObserver(
2    self,
3    name: UIApplication.didBecomeActiveNotification,
4    object: nil
5)

Use global removal only when object is truly done with all notifications.

Testing Observer Lifecycle

Add tests and diagnostics for:

  • callback fires once per event.
  • callback stops after deallocation.
  • no duplicate callbacks after repeated appear/disappear cycles.
  • no retained observer tokens.

Lifecycle-focused tests catch these bugs earlier than manual UI checks.

Common Pitfalls

  • Using one removal strategy for every observer type. Fix by matching cleanup to observer API and intended lifetime.
  • Forgetting to remove block-observer tokens. Fix by storing token and removing it in deinit.
  • Adding observers repeatedly in viewWillAppear without matching remove. Fix with symmetric lifecycle hooks.
  • Updating UI from non-main thread notification callbacks. Fix by dispatching UI work to main queue.
  • Capturing self strongly in long-lived closure observers. Fix with weak capture and explicit lifetime control.

Summary

  • Observer removal location should follow intended observation lifetime.
  • Selector-based observers are commonly removed in deinit.
  • Visibility-scoped observers should be added and removed with view appearance lifecycle.
  • Block-based observers require token-based cleanup.
  • Consistent lifecycle design prevents duplicates, leaks, and stale callbacks.

Course illustration
Course illustration

All Rights Reserved.