Featured image of post Notification, NotificationCenter

Notification, NotificationCenter

Notification

A container for information broadcast through a notification center to all registered observers.

애플 공식문서에서는 노티피케이션을 “모든 등록된 옵저버에게 브로드캐스트되는 정보의 컨테이너"라고 정의하고 있다.

NotificationCenter라는 싱글턴 객체를 통해 이벤트 발생여부를 옵저버를 등록해 놓은 객체들에게 Notification을 post하는 방법으로 사용할 수 있다. Notification.Name이라는 값을 통해서 이벤트를 구분할 수 있다.

NotificationCenter

A notification dispatch mechanism that enables the broadcast of information to registered observers.

애플 공식문서에서는 “등록된 옵저버에게 정보를 브로드캐스트 할 수 있는 발송 매커니즘” 이라고 정의한다.

NotificationCenter ➡️ Notification 사용방법

NotificationCenter에 Observer 등록하기

유투브에서 원하는 채널의 구독 설정을 하는 것과 같이, 관찰하고 싶은 이벤트를 addObserver를 사용해서 등록해야 한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Son {
    let name: String
    var pocketMoney: Int

    init(name: String, pocketMoney: Int) {
        self.name = name
        self.pocketMoney = pocketMoney

        // NotificationCenter에 Observer 등록하기 - 구독 설정
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(answerToDaddy(_:)),
                                               name: Notification.Name.toDo,
                                               object: nil)

        @objc func answerToDaddy(_ noti: Notification) {

            // notification.userInfo 값을 받아온다.
            guard let object = noti.userInfo?[ToDoList.math] as? String else {
                return
            }
            print("\(name): 오늘의 할일은 \(object)입니다.")
        }
    }
}

namepocketMoney을 프로퍼티로 가지는 Son이라는 클라스를 만들고 초기화 시 NotificationCenterObserver를 등록한다.

1️⃣ self : notification의 관찰자(observer)가 될 object ➡️ 여기서는 자기 자신(Son)
2️⃣ selector : notification이 오면 실행할 함수 (등록한 이름의 노티를 받으면 answerToDaddy(_:)를 실행한다.)
3️⃣ name : notification의 이름 (Notification.Name("이름")이렇게 등록하고 post와 맞춰도 된다. 여기서는 Extension으로 지정해 줌)
4️⃣ object : 지정하면 특정 sender로부터만 notification을 받음 (optional)

1
2
3
4
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(answerToDaddy(_:)),
                                               name: Notification.Name.toDo,
                                               object: nil)

⬆️ toDo라는 이름의 notification을 받으면 answerToDaddy(_:)를 실행한다.

NotificationCenter로 Post하기 (발송하기)

.post를 사용하여 NotificationCenter로 Notification을 보낼 수 있다.

Dad라는 클라스에서 toDo라는 이름으로 NotificationNotificationCenter로 발송한다.

1️⃣ name : 발송하고자 하는 notification의 이름 (이걸 통해 알림을 식별)
2️⃣ object : 특정 sender의 notification만 받고 싶은 경우에 작성 (없으면 nil)
3️⃣ userInfo : notification과 관련된 값 = extra data를 보내는데 사용한다. (없으면 nil)
이때 userInfo의 타입은 위에서 말했다시피 [Anyhasable : Any] 이기 때문에 다운캐스팅해서 원하는 타입에 맞춰 작성해주면 된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Dad {
    func callSon() {
        print("what's your chore for Today?")

        // NotificationCenter로 Post 발송
        NotificationCenter.default.post(name: Notification.Name.toDo,
                                        object: nil,
                                        userInfo: [ToDoList.math: "수학 문제풀기"])
    }
}

removeObserver

addObserver 이후에 removeObserver를 사용하여 등록한 Observer를 제거할 수 있다.

If your app targets iOS 9.0 and later or macOS 10.11 and later, and you used addObserver(_:selector:name:object:) to create your observer, you do not need to unregister the observer. If you forget or are unable to remove the observer, the system cleans up the next time it would have posted to it.

애플 문서에서는 앱이 iOS 9 이이상을 타겟으로 한다면 자동으로 옵저버를 정리해 준다고 되어 있지만, 삭제 시점이 내가 원하는 시점이 아닐 수 있으니 deinit을 사용하여 옵저버를 제거해 주는 것이 좋다.

장 / 단점

장점

  • 많은 코드를 작성할 필요 없이 구현이 비교적 간단하다.
  • 일대다 방식으로 여러 객체에 쉽게 알림을 보내줄 수 있다.
  • Notification과 관련된 정보를 Any? 타입의 object, [AnyHashable: Any]? 타입의 userInfo로 전달받아 사용할 수 있다.

단점

  • 제어 프로세스의 추적이 어려울 수 있다.
  • 등록된 개체는 알림센터에서 등록을 취소해주어야 한다.
  • post한 이후의 피드백을 받을 수 없다.
  • 컨트롤러와 관찰자 개체간의 연결 관리를 위해서는 제 3자 개체가 필요하다.

예제코드 풀버전

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
// extension으로 Notification Name 설정 (호출 시 용이)
extension Notification.Name {
    static let toDo = Notification.Name("toDo")
    static let pocketMoney = Notification.Name("pocketMoney")
}

// Notification과 관련된 인스턴스
enum ToDoList {
    case math
    case reading
    case pushUp
}

class Dad {
    func callSon() {
        print("what's your chore for Today?")

        // NotificationCenter로 `toDo`라는 이름의 Post 발송
        NotificationCenter.default.post(name: Notification.Name.toDo,
                                        object: nil,
                                        userInfo: [ToDoList.math: "수학 문제풀기"])
    }

    func askHowMuchSonHave() {
        print("How much do you Have money?")
        // NotificationCenter로 `pocketMoney`라는 이름의 Post 발송
        NotificationCenter.default.post(name: Notification.Name.pocketMoney,
                                        object: nil)
    }
}

class Son {
    let name: String
    var pocketMoney: Int

    init(name: String, pocketMoney: Int) {
        self.name = name
        self.pocketMoney = pocketMoney

        // NotificationCenter에 `toDo`라는 이름의 Observer 등록하기 - 구독 설정
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(answerToDaddy(_:)),
                                               name: Notification.Name.toDo,
                                               object: nil)
        // NotificationCenter에 `pocketMoney`라는 이름의 Observer 등록하기
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(toTellDaddyHowMuchIHave(_:)),
                                               name: Notification.Name.pocketMoney,
                                               object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self,
                                               name: Notification.Name.toDo,
                                               object: nil)

        NotificationCenter.default.removeObserver(self,
                                               name: Notification.Name.pocketMoney,
                                               object: nil)
    }

    @objc func answerToDaddy(_ noti: Notification) {

        // notification.userInfo 값을 받아온다.
        guard let object = noti.userInfo?[ToDoList.math] as? String else {
            return
        }
        print("\(name): 오늘의 할일은 \(object)입니다.")
    }

    @objc func toTellDaddyHowMuchIHave(_ noti: Notification) {

        print("\(name): 제가 가진 용돈은 \(pocketMoney)원 입니다.")
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Dad 인스턴스 생성
        let jae: Dad = Dad()

        // Son 인스턴스 생성 - init하면서 옵저버 등록됨
        let sion: Son = Son(name: "Sion", pocketMoney: 1000)
        let smith: Son = Son(name: "Smith", pocketMoney: 500)
        let matthew: Son = Son(name: "Matthew", pocketMoney: 3800)

        // jae가 NotificationCenter에 post 한다
        jae.callSon()
        jae.askHowMuchSonHave()
    }
}

// what's your chore for Today?
// Sion: 오늘의 할일은 수학 문제풀기입니다.
// Smith: 오늘의 할일은 수학 문제풀기입니다.
// Matthew: 오늘의 할일은 수학 문제풀기입니다.

// How much do you Have money?
// Sion: 제가 가진 용돈은 1000원 입니다.
// Smith: 제가 가진 용돈은 500원 입니다.
// Matthew: 제가 가진 용돈은 3800원 입니다.

Reference

Apple - notification
Apple - notificationcenter
Apple - removeObserver(_:)
Ari - Notification, NotificationCenter
Kio - TIL
Delegate, NOTIFICATION,KVO pros and cons