

Observable 객체가 관찰자에게 전송할 수 있는 이벤트의 종류를 나타냅니다. next(Value)error(Error)completedenum ObservableEvent<Value> {
case next(Value)
case error(Error)
case completed
}
block을 통해 특정 이벤트가 발생했을 때 실행할 코드를 가지고 있습니다.observer 프로퍼티는 weak로 선언되어 있어서 순환 참조를 방지합니다. class Observable<Value> {
/// - observer : 실제 관찰자 객체
/// - block: 값이 변경될 때 실행될 클로저를 저장
struct Observer<Value> {
weak var observer: AnyObject?
let block: (ObservableEvent<Value>) -> Void
}
}
value 라는 프로퍼티를 가지고 있으며, 이 값의 변화를 추적합니다. observers 라는 배열을 통해 모든 관찰자(Observer)를 관리합니다. observe(on: observerBlock:)Observable 인스턴스의 observe(on: observerBlock:) 메서드를 호출하여 자신을 Observer로 등록합니다. notifyObservers(event:)remove(observers:)subscribe(on:disposeBag:onNext:onError:onCompleted:)subscribe 메서드는 Observable 인스턴스에 관찰자를 추가하고, 이 관찰자가 어떻게 상태 변화에 반응할지를 정의하는 역할을 합니다.class Observable<Value> {
/// - observer : 실제 관찰자 객체
/// - block: 값이 변경될 때 실행될 클로저를 저장
struct Observer<Value> {
weak var observer: AnyObject?
let block: (ObservableEvent<Value>) -> Void
}
/// - 모든 observers를 저장하는 배열
private var observers = [Observer<Value>]()
/// - 실제 관찰되는 값
/// - 값이 설정될 때마다 'didSet'에서 'notifyObservers' 메서드를 호출하여 모든 observer에게 알린다.
var value: Value {
didSet { notifyObservers(event: .next(value)) }
}
init(_ value: Value) {
print("Observable Init")
self.value = value
}
deinit {
print("Observable Deinit")
observers.removeAll()
}
/// observer를 추가한다. 추가될 때 현재 값에 대한 알림도 바로 전달한다.
@discardableResult
fileprivate func observe(
on observer: AnyObject,
observerBlock: @escaping (ObservableEvent<Value>) -> Void
) -> Observable<Value> {
observers.append(Observer(observer: observer, block: observerBlock))
observerBlock(.next(self.value))
return self
}
/// 모든 observers에게 값을 알린다
private func notifyObservers(event: ObservableEvent<Value>) {
for observer in observers {
observer.block(event)
}
}
fileprivate func removeDisposable(for observer: AnyObject) -> () -> Void {
return { [weak self] in
self?.remove(observer: observer)
}
}
/// 특정 observer를 제거한다
private func remove(observer: AnyObject) {
observers = observers.filter { $0.observer !== observer }
}
@discardableResult
func subscribe(
on observer: AnyObject,
disposeBag: DisposeBag,
onNext: ((Value) -> Void)? = nil,
onError: ((Error) -> Void)? = nil,
onCompleted: (() -> Void)? = nil
) -> Subscription<Value> {
return Subscription(
observable: self,
observer: observer,
disposeBag: disposeBag,
onNext: onNext,
onError: onError,
onCompleted: onCompleted
)
}
}
Observable 인스턴스의 observe(on:observerBlock:) 메서드를 호출하여 자신을 Observer로 등록합니다.Observable 인스턴스의 value 프로퍼티가 변경되면, didSet을 통해 notifyObservers(event:) 메서드가 호출됩니다.notifyObservers(event:) 메서드는 모든 등록된 Observer에게 변경된 값을 알립니다.block을 통해 정의된 방식으로 변경된 값에 반응합니다.weak 키워드를 사용해야 합니다. error(Error) 케이스를 통해 오류를 처리해야 합니다. DisposeBag 클래스를 사용하여 이를 관리할 수 있습니다. Subscription class는 Observable에 관찰자(observer)를 등록하고, 이벤트가 발생했을 때 적절한 액션을 취하기 위한 메서드를 제공합니다. observableSubscription이 관찰하는 Observable 객체입니다. observerObservable의 변화를 관찰하고 반응합니다.disposeBagSubscription 객체를 저장하고, 필요할 경우 일괄적으로 모두 해제 (dispose)할 수 있습니다. onNextObservable에서 .next 이벤트가 발생했을 때 실행할 클로저를 등록합니다. onErrorObservable 에서 .error 이벤트가 발생했을 때 실행할 클로저를 등록합니다. onCompletedObservable 에서 .completed 이벤트가 발생했을 때 실행할 클로저를 등록합니다. Subscription 객체는 Observable 의 subscribe 메서드를 통해 생성됩니다. 생성된 Subscription 객체에서 Observer를 Observable에 저장하고, disposeBag에 dispose시 발동 할 클로저 함수를 저장하고 deinit 됩니다. /* Observable class와 같은 swift 파일*/
final class Subscription<Value> {
private let observable: Observable<Value>
private weak var observer: AnyObject?
private let disposeBag: DisposeBag
init(
observable: Observable<Value>,
observer: AnyObject,
disposeBag: DisposeBag,
onNext: ((Value) -> Void)? = nil,
onError: ((Error) -> Void)? = nil,
onCompleted: (() -> Void)? = nil
) {
self.observable = observable
self.observer = observer
self.disposeBag = disposeBag
if let onNext = onNext {
self.onNext(onNext)
}
if let onError = onError {
self.onError(onError)
}
if let onCompleted = onCompleted {
self.onCompleted(onCompleted)
}
print("Subscription Init")
}
deinit {
print("Subscription Deinit")
}
@discardableResult
func onNext(_ onNext: @escaping (Value) -> Void) -> Self {
guard let observer = observer else { return self }
let disposable = observable.observe(on: observer) { event in
if case .next(let value) = event {
onNext(value)
}
}.removeDisposable(for: observer)
disposeBag.add(disposable)
return self
}
@discardableResult
func onError(_ onError: @escaping (Error) -> Void) -> Self {
guard let observer = observer else { return self }
let disposable = observable.observe(on: observer) { event in
if case .error(let error) = event {
onError(error)
}
}.removeDisposable(for: observer)
disposeBag.add(disposable)
return self
}
@discardableResult
func onCompleted(_ onCompleted: @escaping () -> Void) -> Self {
guard let observer = observer else { return self }
let disposable = observable.observe(on: observer) { event in
if case .completed = event {
onCompleted()
}
}.removeDisposable(for: observer)
disposeBag.add(disposable)
return self
}
}
Disposebles 클래스는 단일 또는 여러 개의 Disposable을 관리하는 역할을 합니다. Disposable은 클로저를 통해 정의되며, 이 클로저는 특정 리소스 또는 작업을 폐기하는 데 사용됩니다. private해서 동일 위치에있는 DisposeBag 클래스를 제외하곤 다른 곳에서 활용할 수 없게 해줍니다. disposables[() -> Void] 타입의 배열로, 폐기할 수 있는 리소스의 목록을 나타냅니다. 폐기 작업을 수행하는 클로저를 포함하고 있습니다. add()disposables 배열에 추가합니다. dispose()disposables 배열을 비웁니다. 각 클로저를 호출하여 리소스를 해제합니다. disposables.forEach를 통해 Observable의 removeDisposable() 함수를 호출 후 Observable 내부 Observer를 제거 후 disposables 배열을 비웁니다. private final class Disposables {
private var disposables: [() -> Void] = [] {
didSet {
print(disposables)
}
}
func add(_ disposable: @escaping () -> Void) {
disposables.append(disposable)
}
func dispose() {
disposables.forEach { $0() }
disposables.removeAll()
}
}
DisposeBag 클래스는 Observer의 클로저 함수들을 관리하고, 이들을 일괄적으로 해제할 수 있는 기능을 제공합니다 .disposablesDisposables 타입의 인스턴스로, 폐기 가능한 리소스를 실제로 관리합니다. add()Disposables 인스턴스에 추가하는 메서드입니다. clear()Disposeables 인스턴스의 dispose() 메서드를 호출하여 모든 리소스를 폐기하고 비웁니다. 즉, 수동으로 비우게 될 때 활용하는 메서드 입니다. DisposeBag은 deinit 메서드를 통해 자동으로 dispose 메서드를 호출하므로, DisposeBag이 해제되면 관련된 모든 Observer를 자동으로 해제됩니다. 이를 통해 메모리 누수를 방지하고, 코드의 안정성을 높일 수 있습니다.final class DisposeBag {
private weak var disposables: Disposables?
func add(_ disposable: @escaping () -> Void) {
disposables?.add(disposable)
}
func clear() {
disposables?.dispose()
}
deinit {
disposables?.dispose()
}
}
import Foundation
protocol MainViewModelInput {
func viewDidLoad()
func textDidChange(text: String?)
}
protocol MainViewModelOutput {
var textFieldText: Observable<String?> { get }
}
typealias MainViewModelProtocol = MainViewModelInput & MainViewModelOutput
final class MainViewModel: MainViewModelProtocol {
// MARK: Output
let textFieldText: Observable<String?> = Observable(nil)
}
// MARK: Input
extension MainViewModel {
func viewDidLoad() {
}
func textDidChange(text: String?) {
textFieldText.value = text
}
}
import UIKit
final class MainViewController: UIViewController, Alertable {
private var viewModel: MainViewModelProtocol? = MainViewModel()
private let textField = UITextField()
private let label = UILabel()
private let button = UIButton()
private var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
bindViewModel()
}
private func setupUI() {
self.view.backgroundColor = .white
textField.borderStyle = .roundedRect
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
label.numberOfLines = 0
let stackView = UIStackView(arrangedSubviews: [textField, label])
stackView.axis = .vertical
stackView.spacing = 20
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
])
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -16)
])
button.setTitle("disposeBag", for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(cancelSubscribe), for: .touchUpInside)
}
// MARK: Input
@objc private func textFieldDidChange() {
viewModel?.textDidChange(text: textField.text)
}
@objc private func cancelSubscribe() {
button.isSelected.toggle()
if button.isSelected {
disposeBag.clear()
// viewModel = nil
} else {
// viewModel = MainViewModel()
bindViewModel()
}
}
// MARK: Output
private func bindViewModel() {
viewModel?.textFieldText
.subscribe(on: self, disposeBag: disposeBag)
.onNext { [weak self] text in
if let text = text {
self?.label.text = text + "\n" + text
}
}
viewModel?.textFieldText
.subscribe(on: self, disposeBag: disposeBag)
.onNext { text in
if let text = text {
print(text)
}
}
}
}

Observable이 init되고 Subsciption이 init되면서 dispoable과 observer가 추가가 되고 Subscription이 참조된게 없으므로 바로 deinit되는 것을 확인 할 수 있다. Observable의 두 개의 Subscription을 반영했기 때문에 disposables 배열도 두 개가 되는 것을 확인 할 수 있다. 
disposables 내부 배열은 모두 지워지며, Observable 내부의 있는 Observer도 지워져서 기능이 정상작동 하지 않는것을 확인할 수 있다.
disposables 내부 배열도 지워지면서, ViewModel을 nil 처리 하였기 때문에 Observable 또한 Deinit 처리 되는 것을 확인할 수 있다.제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.
포스팅 후 코드를 확인해 보니깐 onNext를 제외한 나머지 onCompleted, onError는 기능을 제대로 할 수 없는 코드네요 ㅎㅎ..감안해주세요~