ObservationExtras is a library with a few additions to the Swift Observation framework. Those tools are mainly aimed to make it easier to use Observation together with UIKit.
There are currently two macros available to make the basic functionality work.
@Observing macro allows you to mark you class as observing. This macro goes hand in hand with @Observable macro. Where you mark ViewModel as @Observable, you mark the recipient as @Observing. In a real world scenario, it would look something like this:
@Observable
final class ViewModel {}
@Observing
final class ViewController: UIViewController {
let viewModel = ViewModel()
}
Additionally, we need to somehow notify the system to start observing the state changes. There are two ways to go about this:
-
If your class subclasses any of the provided classes, use the
@Observing(providesInheritance: true)macro to automatically start observing changes. This is acchieved by calling into theobserveState()method in superclassviewDidLoad().- Currently provided class is ObservingUIViewController
- You can use this class wherever you would use UIViewController.
- To automate this process for any other class, create a custom class inheriting from your class, provide
func observeState()and call it at the entrypoint. This allows the subclass to automatically start observing state changes. - Example:
-
class ObservingUITableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() observeState() } func observeState() {} } @Observing(providesInheritance: true) final class MyTableViewController: UITableViewController { // super.observeState() get automatically called in super.viewDidLoad() // the macro generates override func observeState() instead of private func observeState() it would if used as just @Observing }
-
if your class does not subclasss from this type of superclass, observation is achieved by calling the observeState() generated method on entrypoint, so generally in viewDidLoad event when using ViewControllers.
- example:
-
@Observing // or @Observing(providesInheritance: false) class MyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() observeState() // method generated by macro } }
@observeState macro lets you specify which function access state and should be called again when the state changes. This allows you to be as granular with state updates as you need, because the function will be called again only if it accesses given piece of state, not when any state changes.
Example scenario:
@Observable
final class ViewModel {
var isButtonHidden = false
var buttonText = "Press me
}
@Observing
final class ViewController: UIViewController {
let viewModel = ViewModel()
let button = UIButton()
@observableState
private func setupButtonState() {
button.isHidden = viewModel.isButtonHidden
button.setTitle(viewModel.buttonText, for: .normal)
}
}
In this scenarion, if any of the accesses variables change, the whole setupButtonState() function is called. But if we did something like this:
@Observable
final class ViewModel {
var isButtonHidden = false
var buttonText = "Press me
}
@Observing
final class ViewController: UIViewController {
let viewModel = ViewModel()
let button = UIButton()
@observableState
private func setupButtonVisibilityState() {
button.isHidden = viewModel.isButtonHidden
}
@observeState
private func setupButtonTitle() {
button.setTitle(viewModel.buttonText, for: .normal)
}
}
Here, only the function which accesses the variable would be called when the variable changes.