Observer Pattern in Swift

Curiosity about observer pattern

So why we pass it which is already have in our observer classes? Why not just use them?

In update, you can refer to the observed subject just by saying weatherObj. This would only work in this specific case where the observer does have the subject as one of its properties, but that's not necessarily true in general.

One observer can observe multiple subjects, then in update, you'd most likely want to know which one of subjects that you are observing got updated.

The observer can also not have the subject as a property. For example, if you did this in CurrentConditionsDisplay instead:

weatherObj.registerObserver(obs: SomeOtherObserver())

Then in SomeOtherObserver.update, it can't use weatherObj to refer to the observed subject, because it's not CurrentConditionsDisplay anymore.

Passing the observed subject as the parameter of update makes it very convenient for whoever observing to get data out of the subject, not just CurrentConditionsDisplay.

Shouldn't I use this declaration inside observer classes as weak reference(private let weatherObj: WeatherData)?

Yes, there is indeed a strong reference cycle in the code, and making weatherObj weak would break the cycle. However, I think making the array of observers weak references would make more sense. KVO has a similar behaviour, namely that adding observers don't create a strong reference to them. It's not very trivial to make an array of weak references though.

How do I make an Observer pattern with 2 Swift protocols, where the two associatedtypes must be the same?

I'll simplify your use case a bit (ignore observations) to hopefully get the concept across.

HasObservers basically has 2 associated types - the DataType and the IsObserver type, and then you'd constrain the IsObserver type to have the right DataType

protocol IsObserver {
associatedtype DataType
func dataDidUpdate(_ data: [DataType])
}

protocol HasObservers {
associatedtype DataType
associatedtype ObserverType: IsObserver where ObserverType.DataType == DataType

static func addObserver(_ observer: ObserverType)
static func tellObserversDataDidUpdate(_ data: [DataType])

// ..
}

How to create a class instance in Observer pattern?

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.

Observer Communication Pattern

You're using a UITabBarController to host the views. The system initially only loads the view controller it needs to display (ViewController in this case). Then, once you click on the tab for the SecondVC, it loads that view controller.

You can verify this by putting a print statement in viewDidLoad of SecondVC.

You can also verify that if you navigate to SecondVC before going back to ViewController and pressing Notify, both view controllers get notified in this scenario.

So, it's not a bug -- it's just an implementation detail of when the views are loaded.

If you want to find a way to make sure that SecondVC has access to that information when it's loaded, you have two options:

  1. Rely on a different system of propagating the state
  2. Put your notification listener in required init?(coder: NSCoder) instead of viewDidLoad (this does get called during setup). This has a caveat though: the UILabel won't be loaded yet, so you'd have to store that state for loading later. Trying to access mySecondLabel before viewDidLoad will result in a crash.

Update
Updated code that stores the Notification in the event that you want to use the init method:

class SecondVC: UIViewController {

// MARK: - Properties
@IBOutlet weak var mySecondLabel: UILabel?

var haveBeenNotified = false //to store whether the notification has been seen

required init?(coder: NSCoder) {
super.init(coder: coder)
notificationCtr.addObserver(self, selector: #selector(notifyObserverstoo), name: NSNotification.Name(rawValue: MyNotifications.broadcast), object: nil)
}

// MARK: - Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()

print("Loaded second view controller")

if haveBeenNotified {
mySecondLabel?.text = "Was notified before viewDidLoad"
}
}

// MARK: - Observer Selector Functions
@objc func notifyObserverstoo() {
haveBeenNotified = true
mySecondLabel?.text = "I got Notified too"
}

}


Related Topics



Leave a reply



Submit