2024. 5. 14. 12:02ㆍArchitecture, Design Pattern
1️⃣ 옵저버 패턴 (Observer Pattern) 기본 개념 살펴보기
옵저버 패턴 (Observer Pattern)의 개념은 매우 간단하다.
한 객체에서 일어나는 정보를 다른 특정한 여러 객체들에게 전달할 때 사용하는 디자인 패턴이다.
여기서 말하는 "객체에서 일어나는 정보"란 데이터의 추가/수정/삭제와 같은 변경사항, 객체에서 발생한 이벤트 등을 의미한다고 이해하면 되겠다.
예시나 자세한 코드로 디자인 패턴을 살펴보기 전에, 앞으로 사용될 기본 개념들에 대해 살펴보고 넘어가 보자!
*용어도 각 예시마다 다양하게 바뀌면서 사용되니까 모두 동일한 느낌으로 혼용해서 사용된다고 이해하면 좋겠다.
Subject, Publisher (주제)
: 옵저버들이 관심있게 보는 객체 (주제) = 특정한 이벤트가 발생되거나, 상태가 변하는 객체
Observer, Subscriber (관찰자, 구독자)
: 관심있는 주제의 상태변화를 관찰하고 그에 따라 반응하는 객체
attach, addObserve, Subscribe (구독, 등록)
: 관찰자가 특정 주제를 관찰하게 되는 것 = Observer 객체 내의 observers 배열에 추가되는 것
Notify (알림)
: 주제가 상태 변화를 관찰자에게 알려주는 것
2️⃣ 인스타그램 알림 예시로 옵저버 패턴 (Observer Pattern) 이해하기
💡 옵저버 패턴 (Observer Pattern)은 어떤 객체의 상태가 변하면 그 객체에 의존하는 모든 객체에게 변경된 정보가 전달되는 디자인 패턴이다.
예시를 하나 가지고 옵저버 패턴을 설명해 보겠다.
어떤 특정한 인스타그램 계정이 있고 그 계정에 대해 게시글 알림 설정을 해둔 다른 팔로워 계정들이 새로운 게시글이 업데이트될 때마다 알림을 받고자 하는 구조를 예시로 들어보겠다.
우선 알림을 전달하는 특정 인스타그램 계정을 만들어주자.
"관찰자의 대상"이 되는 이 객체는 위에서도 말했듯이 Subject(주제)라고 불리며, 이 예시에서는 다른 사람들이 많이 게시글 알림 설정을 해두는 (인플루언서) 인스타그램 계정이 될 것이다.
이번 예시에서는 특정 인플루언서 인스타그램 계정 하나만 만들 것이지만,
본래 Subject 객체는 여러 개로 생성될 수 있기에 재사용이 가능한 프로토콜(protocol)로 필요한 메서드를 먼저 정의해 둔다.
필요한 메서드에는 Subject를 Observe하겠다고 추가하는 메서드와, Observer들에게 새로운 정보를 알리는 메서드, 그리고 여기에는 구현하지 않았지만 Observe를 해제하는 메서드도 반복해서 Subject 객체에 사용될 것이다.
protocol Subject {
func addObserver(_ observer: Observer)
func notifyObserver(_ post: Post)
}
이제 실제로 이번 예시에서 사용될 인플루언서 인스타그램 계정 (Subject)을 만들었다.
해당 계정에는 알림 설정을 해둔 Observer들을 저장해두는 배열과,
계정에 게시글을 추가하는 메서드, (= 객체의 상태를 변화시키는 메소드라고 이해하면 된다!)
그리고 Subject 프로토콜의 구현부를 추가해두었다.
class SoptInstagram: Subject {
// 해당 Account의 신규 게시글 정보를 받고자 설정한 Observer들의 리스트
private var observers = [Observer]()
// 해당 Account의 알림을 설정할 때 사용하는 메서드
func addObserver(_ observer: Observer) {
observers.append(observer)
}
// 인스타그램 게시글 추가 -> 알림 메서드 호출
func addPost(_ post: Post) {
notifyObserver(post)
}
// 알림 메서드 : 해당 Account의 모든 Observer들에게 게시물 업데이트 알림 발송
func notifyObserver(_ post: Post) {
observers.forEach {
$0.update(post: post)
}
}
}
주제, Subject를 만들었으니 그 주제롤 관찰/구독할 객체들도 만들어주겠다.
역시 Subject와 마찬가지로 관찰자의 형태도 여러 개일 수 있으니 프로토콜로 먼저 공통된 메서드를 만들어주겠다.
Observer에서 필요한 공통 메서드는 딱 하나이다.
Subject에서 변경된 사항이 있다고 전달이 오면 그에 맞게 대응할 부분이다! 아래 Concrete Observer에서 어떻게 구현되는지 더 자세하게 알아보자.
protocol Observer {
func update(post: Post)
}
예시로 두 가지 객체를 모두 만들었다.
*인스타그램 계정 팔로우를 하지 않아도 알림 설정만 할 수 있다는 가정!
위 Subject에서 notify 메서드에서 각 Observer들마다 update 메서드를 통해 변경사항을 알리는 코드가 있었는데,
각 Observer들의 메서드 구현부에서는 그에 따른 대응 로직을 추가해주면 된다.
class Follower: Observer {
private let userName: String
init(userName: String) {
self.userName = userName
}
func update(post: Post) {
print("\(userName)님 지금 솝트 인스타그램에 신규 게시글이 업데이트 되었어요!")
}
}
class NonFollower: Observer {
func update(post: Post) {
print("지금 솝트 인스타그램에 신규 게시글이 업데이트 되었어요! 팔로우하고 더 많은 정보를 알아봐요!")
}
}
자 이제 사용 코드를 살펴보자.
Subject가 될 인스타그램 객체와 유저 객체를 생성한다.
그리고 user1과 user2는 Subject 인스타그램 계정에 대해 알림 설정을 신청했다. -> Observer로 등록 (addObserver)
그러면 Subject 계정에 새로운 게시글이 추가된 순간, (addPost(newPost))
해당 계정을 Observe하고 있는 user1과 user2에게 모두 업데이트 알림이 전달이 되는 것이다!
Observe하고 있지 않은 user3은 Subject 객체에 새로운 게시글이 추가가 되었는지, 어떤 변화가 일어났는지 등을 알 수가 없는 것.
let soptInsta = SoptInstagram()
let user1 = Follower(userName: "미니민")
let user2 = Follower(userName: "민재")
let user3 = Follower(userName: "민버")
soptInsta.addObserver(user1)
soptInsta.addObserver(user2)
let newPost = Post(content: "앱잼 공지 어쩌구저쩌구")
soptInsta.addPost(newPost)
// 미니민님 지금 솝트 인스타그램에 신규 게시글이 업데이트 되었어요!
// 민재님 지금 솝트 인스타그램에 신규 게시글이 업데이트 되었어요!
3️⃣ 우리는 은연중에 이미 Observer Pattern을 사용하고 있었다는 사실! - NotificationCenter
위의 구조를 듣고 혹시나 "어? 이거 뭔가 익숙한데?"라고 생각이 들었다면 옵저버 패턴을 잘 이해하고 있다는 뜻이다.
이런 생각이 들지 않았더라도 지금부터 설명할 내용을 읽으면 "아!"라고 하며 무릎을 탁 치게 될지도 모르겠다.
데이터 전달 방식 중 NotificationCenter가 바로 Observer Pattern을 따르는 방식이다.
알림 센터라는 이름에 맞게 NotificationCenter는 말 그대로 "방송국" 같은 개념인데,
원리는 해당 NotificationCenter로 특정한 데이터가 post 되면 -> 그 NotificationCenter를 addObserver 해둔 다른 객체들의 각 메서드가 반응하게 되는 방식이다!
그냥 쉽게 말해 인스타그램 객체로 말했던 녀석들을 뷰컨(ViewController)로 개념을 바꾸기만 한다면,
동시적이고 자동적으로 뷰 컨트롤러 간 데이터를 전달하는 효율적인 하나의 방식으로 Observer Pattern이 활용될 수 있다는 것이다.
Notification 데이터 전달 예시도 한번 살펴보자.
*ProviderVC에서 textField로 새로운 text를 입력받으면, ReceiverVC에서 UILabel의 내
Notification 안에는 어러 개의 알림이 등록될 수 있는데, 이를 Notification.name으로 구분하게 된다.
나는 텍스트를 보내고 받는 예시를 위해 sendNewText라는 가상의 신호 이름을 만들어줬다.
extension Notification.name {
static let sendNewText = Notification.Name("sendNewText")
}
데이터가 들어오는 뷰컨트롤러에서는 텍스트필드로부터 text를 입력받고, 그에 따라 NotificationCenter로 post를 보낸다.
// ProviderViewController
func sendData() {
guard let text = textField.text else { return }
NotificationCenter.default.post(name: .sendNewText, object: text)
}
그럼 해당 신호(.sendNewText)를 Observe하고 있는 다른 뷰컨트롤러에서는 새로운 post가 들어올 경우,
자동으로 addObserver에 붙어있는 selector 함수가 호출되어 새로운 값에 따른 처리를 구현할 수 있게 된다!
보면 알겠지만, reloadData가 필요한 테이블뷰나 컬렉션뷰에서 값 변경에 따른 알림을 받고 업데이트하는데 유용하게 쓰일 수 있다는 점.
// ReveiverViewController
var data: [String] = [] {
didSet {
collectionView.reloadData()
}
}
NotificationCenter.default.addObserver(self,
selector: #selector(updateData:),
name: Notification.Name(.sendNewText),
object: nil)
@objc func updateData(notification: Notification) {
if let userInfo = notification.object as? String {
data.append(text)
}
}
NotificationCenter의 더 세부적인 정보가 알고 싶다면, 아래 공식문서를 참고하길 바란다.
4️⃣ 이 외에도 Observer Pattern은 정말 많이 사용된다!
지금 설명한 내용 외에도 MVVM의 ViewModel에서 Observable을 두어 데이터의 수정을 자동으로 알리는 방식이나,
RxSwift나 Combine 프레임워크 같이 선언형 프로그래밍 방식으로 데이터의 변화를 체크하는 것도 모두 이 Observer Pattern의 개념을 기반으로 만들어졌다고 보면 된다!
자세한 내용은 아래 글로 이어가보자!