Hello,
I am attempting to make a system where a 'Signal Monitor' watches several 'Signal Generators' and something happens if one of the signal generators, well, generates a signal.
I thought that a pair of protocols would work well here. I have defined the protocols and also extended those to provide built-in implementations for some functions.
I want one Monitor and several Signal Generators. I have made them classes as I want them to be able to mutate themselves and I want the same instances to stick around.
The Monitor keeps track of the Signal Generators via an array. The Signal Generators keep a reference to the Monitor so that they can signal back to it.
This is all fine. Every time I make a new Signal Generator I can set its monitor property manually and add it to the tracking array manually. However, I thought that wouldn't it be nice to add a 'startMonitoring:signalGenerator' function to the Monitor so that it would both add the new generator to the array and also set the monitor property on the generator instance. I also thought that this would be good to put into the protocol extension so that there's an automatic implementation for any conforming type.
However this doesn't work and fails with an error Cannot use mutating member on immutable value: 'self' is immutable
Which surprises me, because all the relevant instances are vars and therefore should be immutable. When I replicate the functionality directly into the type it works.
What's the difference? What am I doing wrong?
Thanks.
import Foundation
@main
public struct GateControlMain {
public private(set) var text = "Hello, World!"
public static func main() {
let runLoop = RunLoop.current
let distantFuture = Date.distantFuture
/// Set this to false when we want to exit the app...
let shouldKeepRunning = true
var gc = GateControl()
gc.doSomething()
gc.start()
// Run forever
while shouldKeepRunning == true &&
runLoop.run(mode:.default, before: distantFuture) {}
}
}
// Gate Signal Generation Protocols
/// A Type that can signal to the monitor that something has changed and it thinks that the gate should now be open.
protocol GateSignalGenerator {
/// The Gate Signal Monitor (sort of like a delegate)
var gateSignalMonitor: GateSignalMonitor? { get set }
/// Should the gate be open as far as this signal generator is concerned?
func shouldBeOpen() -> Bool
/// Casues the Gate Signal Monitor to poll all signal generators because one of them has changed state.
func triggerPollingOfAllSignals()
}
extension GateSignalGenerator {
// Default Implementation
func triggerPollingOfAllSignals() {
guard let monitor = self.gateSignalMonitor else {
print("No monitor set for \(self)!!!!")
return
}
monitor.requestToPollSignalGenerators(sender: self)
}
}
/// A Type that monitors gate signals and when informed of a change, checks them all.
protocol GateSignalMonitor {
/// All the monitored signals
var gateSignalGenerators: [GateSignalGenerator] { get set }
/// Add a signal generator to our observed signals
mutating func startMonitoringSignalGenerator(_ sigGen: GateSignalGenerator)
/// A way for the signals to notify the monitor that something has changed
func requestToPollSignalGenerators(sender: GateSignalGenerator)
}
extension GateSignalMonitor {
/// Adds the signal generator and makes sure that it's 'monitor' property is correctly set.
mutating func startMonitoringSignalGenerator(_ sigGen: GateSignalGenerator) {
gateSignalGenerators.append(sigGen)
// sigGen.gateSignalMonitor = self // Can't do this as we would not be altering the original sigGen, only a copy, even if the sigGen param is marked as inout.
}
}
/// Controls the opening and closing of the gate
class GateControl : GateSignalMonitor {
var gateSignalGenerators = [GateSignalGenerator]()
var thingThatMutates = "I should change"
var gateIsClosed = true
var testTimer = ExampleOnOffTimer()
init() {
self.setupLogging()
log.info("Gate Control Starts")
}
// GateSignalMonitor Protocol
func requestToPollSignalGenerators(sender: GateSignalGenerator) {
log.info("\(sender) requested a poll of all signal generators")
// Check to see if any of them want the gate open.
}
func doSomething() {
print(thingThatMutates)
thingThatMutates = "I have changed"
print(thingThatMutates)
}
func start() {
testTimer.start()
testTimer.gateSignalMonitor = self
// THIS FAILS (Cannot use mutating member on immutable value: 'self' is immutable)
startMonitoringSignalGenerator(testTimer)
// THIS WORKS
gateSignalGenerators.append(testTimer)
}
}
class ExampleOnOffTimer: GateSignalGenerator {
...... code snipped here .....
}