Let's say I have very simple class:
class Box<T> {
var boxedObject:T
init(object: T) {
self.boxedObject = object
}
}
What I would like to achieve now is to add delegate, which can inform me that value in box has changed:
protocol BoxDelegate<T>: class {
func valueInBoxChanged(box: Box<T>) -> Void
}
class Box<T> {
var boxedObject: T {
didSet {
self.delegate?.valueInBoxChanged(self)
}
}
weak var delegate: BoxDelegate<T>?
init(object: T) {
self.boxedObject = object
}
}
This code is of course not working, because we don't have generic delegates. I can make delegate to be a struct with closure, but it's a bit ugly solution. How I should do such things in Swift?
Let say that instead of BoxDelegate you have a class, and let's call it Listener :
public typealias ValueChanged <T> = (T) -> Void
public class Listener<T> {
var valueChanged: ValueChanged<T>
public init(_ valueChanged: @escaping ValueChanged<T>) {
self.valueChanged = valueChanged
}
}
Box can now be written like that:
class Box<T> {
// the listener needs to be weak in order to be deallocated when the that is
// observing the changes is deallocated
weak private(set) var listener: Listener<T>?
init(_ listener: Listener<T>) {
self.listener = listener
}
}
and you can now have a wrapper class for the properties whose state you need to observe:
class Observed<T> {
var listeners: [Box<T>] = []
private let queue: DispatchQueue = .main
var value: T {
didSet {
notifyListeners()
}
}
func notifyListeners() {
notifyListeners(value)
}
func notifyListeners(_ value: T) {
queue.async { [weak self] in
self?.listeners.forEach { (box) in
box.listener?.valueChanged(value)
}
}
}
func bind(_ listener: Listener<T>) {
listeners.append(Box(listener))
}
func bind(_ listener: @escaping ValueChanged<T>) -> Listener<T> {
let listener = Listener(listener)
listeners.append(Box(listener))
return listener
}
init(_ value: T) {
self.value = value
}
}
and even have a property wrapper:
@propertyWrapper
struct Bind<T> {
let observable: Observed<T>
init(wrappedValue: T) {
observable = Observed(wrappedValue)
}
var wrappedValue: T {
get {
observable.value
}
set {
observable.value = newValue
}
}
var projectedValue: Observed<T> {
observable
}
}
usage :
var text = ""
class ViewModel {
@Bind
var isOn: Bool = false
}
let viewModel = ViewModel()
let bond = viewModel.$isOn.bind { isOn in
if isOn {
text = "on"
} else {
text = "off"
}
}
viewModel.isOn = true
RunLoop.main.run(until: Date().advanced(by: 0.1))
text
viewModel.isOn = false
RunLoop.main.run(until: Date().advanced(by: 0.1))
text

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With