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:
- Rely on a different system of propagating the state
- Put your notification listener in
required init?(coder: NSCoder)
instead ofviewDidLoad
(this does get called during setup). This has a caveat though: theUILabel
won't be loaded yet, so you'd have to store that state for loading later. Trying to accessmySecondLabel
beforeviewDidLoad
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
Cllocation Distancefromlocation (In Swift)
How to Add Uivibrancyeffect to an Existing Uilabel ( Iboutlet )
What Does "Constrain to Margins" Mean in Interface Builder in Xcode 6.0.1
Firebase Sms Verification on iOS - 'Token Mismatch'
Swift 3:Appdelegate Does Not Conform to Protocol Gidsignindelegate
Glkit VS. Metal Perspective Matrix Difference
Swift Probability of Random Number Being Selected
Bit Field Larger Than 64 Shifts in Swift
A Codable Structure Contains a Protocol Property
Float Is Not Convertible to 'Mirrordisposition' Swift What Is Mirrordisposition
Swift Alternative to Respondstoselector:
Adding a Custom Font to MACos App Using Swift
How to Turn Off Color Literals in Xcode