Search code examples
iosswiftobserver-pattern

How to create a class instance in Observer pattern?


I implemented an observer pattern in the below code. However, I am not sure how to create SeasonSubject class's instance in order to call addObserver() function? I don't want to create it inside my view controller. Please refer to below code.

//
//  SeasonViewController.swift
//  PhotoCalender
//
//  Created by Suraj M Gaikwad on 09/07/21.
//

import UIKit

enum Season: String {
    case summer
    case winter
    case monsoon
    case none
}

protocol SeasonObserver {
    func onSeasonChange(_season: Season)
}

class PhotoframeObserver: SeasonObserver {
    var delegate: SeasonViewControllerDelegate?
    
    init(_delegate: SeasonViewControllerDelegate) {
        delegate = _delegate
    }
    
    func onSeasonChange(_season: Season) {
        switch _season {
        case .monsoon, .summer, .winter:
            delegate?.changeTheLayoutPer(_season: _season)
        case .none:
            debugPrint("none")
        }
    }
}

class PhotoDetailsObserver: SeasonObserver {
    
    var delegate: SeasonViewControllerDelegate?
        
    init(_delegate: SeasonViewControllerDelegate) {
        delegate = _delegate
    }
    
    func onSeasonChange(_season: Season) {
        switch _season {
        case .monsoon, .summer, .winter:
            delegate?.changeTheTitle(_season: _season)
        case .none:
            debugPrint("none")
        }
    }
}

protocol SeasonSubjectProtocol {
    func informTheSeasonChange(_season: Season)
}

class SeasonSubject: SeasonSubjectProtocol {
    
    private var _season = Season.none
        
    var changedSeason: Season {
        get {
            _season
        }
        set {
            _season = newValue
        }
    }
    
    private var seasonObserver = [SeasonObserver]()
    
    func addObserver(_observer: SeasonObserver) {
        seasonObserver.append(_observer)
    }
    
    func removeObserver(_observer: SeasonObserver) {
//        seasonObserver.remove(at: 0)
    }
    
    private func notifyObserver() {
        seasonObserver.forEach { $0.onSeasonChange(_season: _season)
        }
    }
    
    func informTheSeasonChange(_season: Season) {
        changedSeason = _season
    }
    
    deinit {
        seasonObserver.removeAll()
    }
}


protocol SeasonViewControllerDelegate {
    func changeTheLayoutPer(_season: Season)
    func changeTheTitle(_season: Season)
}

class SeasonViewController: UIViewController {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var backgroundView: UIView!
    @IBOutlet weak var seasonTitle: UILabel!
    
    var delegate: SeasonSubjectProtocol?

    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        self.imageView.image = UIImage()
        self.backgroundView.backgroundColor = .white
        self.seasonTitle.text = "none"
    }
    
    @IBAction func changeTheSeason(_ sender: UIButton) {
        if count > 2 {
            count = 0
            return
        }
        
        if count == 0 {
            delegate?.informTheSeasonChange(_season: Season.summer)
        }
        if count == 1 {
            delegate?.informTheSeasonChange(_season: Season.winter)
        }
        if count == 2 {
            delegate?.informTheSeasonChange(_season: Season.monsoon)
        }
        
        count += 1
    }
}

extension SeasonViewController: SeasonViewControllerDelegate {
    
    func changeTheLayoutPer(_season: Season) {
        switch _season {
        case .monsoon:
            self.imageView.image = UIImage()
            self.backgroundView.backgroundColor = .gray
        case .summer:
            self.imageView.image = UIImage()
            self.backgroundView.backgroundColor = .orange
        case .winter:
            self.imageView.image = UIImage()
            self.backgroundView.backgroundColor = .blue
        case .none:
            debugPrint("none")
            self.imageView.image = UIImage()
            self.backgroundView.backgroundColor = .white
        }
    }
    
    func changeTheTitle(_season: Season) {
        switch _season {
        case .monsoon, .summer, .winter, .none:
            self.seasonTitle.text = _season.rawValue
        }
    }
}

I want to create an instance of SeasonSubject() class. I want to call addObserver & removeObserver() methods.


Solution

  • You can create SeasonSubject as a singleton instance, it already maintains an array of observers so multiple observers can use this same instance throughout the app.

    class SeasonSubject: SeasonSubjectProtocol {
        static let shared = SeasonSubject()
    }
    

    All you need to do now is - call addObserver / removeObserver on this singleton instance from the places you want.

    class ViewController: UIViewController {
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            SeasonSubject.shared.addObserver(self)
        }
    
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            SeasonSubject.shared.removeObserver(self)
        }
    }
    

    CAUTION : You must make sure these add/remove calls are balanced otherwise you will fall into the trap of observers never getting deallocated.

    If you want to stay away from this problem - you should consider a NotificationCenter based implementation where your observers are never at risk of being retained in memory forever.