[iOS/Swift] Notification Center, Delegate, KVO??

Youngwoo Lee·2021년 3월 16일
3

iOS

목록 보기
1/46
post-thumbnail

타입끼리의 Communication이 있어야 하지만, Coupling이 강해지는 것이 싫어질 경우 사용하는 방법이다. 해당 방법들은 MVVM 패턴에서 Binding 을 하는 과정에도 많이 사용됨으로 반드시 알아야만 한다

Notification

일단 바로 공식문서를 살펴보면

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

그러니깐, Notification은 "알림 센터를 통해 등록된 모든 관찰자에게 브로드캐스트되는 정보 컨테이너"라고 한다.

주요 프로퍼티를 보면

init(name: Notification.Name, object: Any? = nil, userInfo: [AnyHashable : Any]? = nil)

name : 알림을 식별하는 태그
object : 발송자가 옵저버에게 보내려고 하는 객체, 주로 발송자 객체를 전달하는 데 쓰임
userInfo : 노티피케이션과 관련된 값 또는 객체의 저장소(Dictionary)

추가 정보 : 이름을 등록할 때 다음과 같이하면 효율적이다

extension Notification.Name {
    static let changeLabel = Notification.Name("changeLabel")
}
import Foundation

class Teacher {
    func callStudent() {
        NotificationCenter.default.post(name: NSNotification.Name.hey,
                                        object: nil,
                                        userInfo: [NotificationKey.iam: "I'm"])
    }
}

class Student {
    let name: String
    
    init(name: String) {
        self.name = name
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(answerToTeacher(noti:)),
                                               name: Notification.Name.hey, object: nil)
    }
    
    @objc func answerToTeacher(noti: Notification) {
        guard let height = noti.userInfo?[NotificationKey.iam] as? String else {
            return
        }
        print("\(height) \(name)")
    }
}

extension Notification.Name {
    static let hey = Notification.Name("hey")
}

enum NotificationKey {
    case iam
}

let yagom = Teacher()

let kane = Student(name: "kane")
let james = Student(name: "james")
let tak = Student(name: "tak")

yagom.callStudent()
//I'm kane
//I'm james
//I'm tak

Notification Center

Notification Center 는 등록된 옵저버에게 동시에 노티피케이션을 전달하는 클래스이다. NotificationCenter 클래스는 노티피케이션을 발송하면 노티피케이션 센터에서 메세지를 전달한 옵저버의 처리할 때까지 대기한다. 즉, 흐름이 동기적(synchronous)으로 흘러간다. 노티피케이션을 비동기적으로 사용하려면 NotificationQueue를 사용하면 된다.

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

기본 Notification Center 얻기

  • default : 애플리케이션의 기본 노티피케이션 센터다

옵저버 추가 및 제거

  • addObserver(_:selector:name:object:) : 노티피케이션을 노티피케이션 센터의 메서드를 가리키는 장소에 이름을 추가한다
 func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?,object anObject: Any?)
                       
  • removeObserver(_:name:object:) : 노티피케이션 센터의 메서드를 가리키는 장소에서 일치하는 이름을 제거한다
func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?)
  • removeObserver(_:) : 노티피케이션 센터의 메서드를 가리키는 장소에서 모든 이름을 제거한다
 func removeObserver(_ observer: Any)

Notification 발송

Notification을 만들어서 발송!!!!

  • post(_ :) : 지정된 노티피케이션을 노티피케이션 센터에 발송한다
func post(_ notification: Notification)
  • post(name:object:userInfo:) : 지정된 이름, 보낸 객체, 보낼 정보로 노티피케이션을 만들어 노티피케이션 센터에 발송한다
func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable: Any]? = nil)
  • post(name: object:) : 지정된 이름, 보낸 객체로 노티피케이션을 만들어 노티피케이션 센터에 발송한다
func post(name aName: NSNotification.Name, object anObject: Any?)
import Foundation

class Teacher {
    func callStudent() {
        let noti = Notification(name: Notification.Name.hey)
        NotificationCenter.default.post(noti)
    }
}

class Student {
    let name: String
    
    init(name: String) {
        self.name = name
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(answerToTeacher(noti:)),
                                               name: Notification.Name.hey, object: nil)
    }
    
    @objc func answerToTeacher(noti: Notification) {
        print("Ok Teacher I'm coming. My name is \(name)")
    }
}

extension Notification.Name {
    static let hey = Notification.Name("hey")
}

let yagom = Teacher()

let kane = Student(name: "kane")
let james = Student(name: "james")
let tak = Student(name: "tak")

yagom.callStudent()

Delegate 패턴

iOS를 처음 접할 때 모두들 UITableViewDelegate 를 통해서 Delegate 패턴을 접했을 것이다. Delegate 패턴은 단어 그대로 해당 타입의 특정 Method 구현을 다른 객체에게 위임 하는 것이다

protocol StudentDelegate: AnyObject {
    func goSchool()
}

class Student {
    weak var delegate: StudentDelegate?
    
    func wakeUp() {
        delegate?.goSchool()
    }
}

class Parent: StudentDelegate {
    func goSchool() {
        print("아들을 깨워서 밥을 먹이고 학교에 데려다 준다")
    }
}


let student = Student()
student.wakeUp()
//동작을 위임했지만 delegate가 없어서 출력 X

let parent = Parent()
student.delegate = parent
//delegate로 parent가 선임되어서 parent에서 정의한 goSchool이 실행됨
//아들을 깨워서 밥을 먹이고 학교에 데려다 준다

student.wakeUp()

KVO(Key-Value-Observing)

참고)
https://jcsoohwancho.github.io/2019-11-30-KVO(Key-Value-Observing)/

KVO는 A객체에서 B객체의 프로퍼티가 변화됨을 감지할 수 있는 패턴이다. 메소드나 다른 액션에서 나타나는 것이 아니라 프로퍼티의 상태에 반응하는 형태다

장점

  • 두 객체 사이의 정보를 맞춰주는 것이 쉬움
  • new/old Value를 쉽게 얻을 수 있음
  • removeObserve를 따로 해주지 않아도 된다
  • 최신값과 이전값을 같이 제공해준다
  • 객체의 구현을 변경하지 않고 내부 객체의 상태 변화에 대응할 수 있음.

단점

  • NSobject를 상속받는 객체에서만 사용이 가능함
  • 많은 value를 감지할 때에는 많은 조건문이 필요하다
  • Swift 스타일로 KVO를 사용하는 것이 최근에 추가되었지만, Objec-c 스타일이 아직 많이 사용되어서 주의해야된다

덧붙여서 Swift 4 전까지는 NSObject의 메소드인 observeValue(forKeyPath:change:context:)를 오버라이드하여 옵저버를 추가했으나 Swift4부터는 구독하고 싶은 프로퍼티에 observe()를 추가하여 클로저로 사용할 수 있게 하였다. 그러나 Swift 상에서는 didSet이나 willSet 같은 것으로 충분히 대체가 가능할 것 같아 굳이 써야하나 싶은 패턴인 것 같다.


import  Foundation

class SomeClass: NSObject {
  @objc dynamic var value: String = "real"
}

let someObject = SomeClass()

let observing = someObject.observe(\.value, options: [NSKeyValueObservingOptions.initial]) { (object, change) in
  print("SomeClass object value changed to \(object.value)")
}

someObject.value = "test"
//SomeClass object value changed to real
//SomeClass object value changed to text
profile
iOS Developer Student

0개의 댓글