Swift 5, the "Exclusive Access to Memory" enforcement is now on by default for release builds as mentioned in this Swift.org blog post:
Swift 5 Exclusivity Enforcement
I understand the reasoning behind this feature, but with the new Combine framework I feel as if some very normal design patterns are now going to break and I'm curious how best to work around them.
With Combine it's natural for parts of your code to react to changes in a model such that they might need to read from the very property that the model has just changed. But they can no longer do that because it will trigger a memory exception as you attempt to read a value that is currently being set.
Consider the following example:
struct PasswordProposal { let passwordPublisher = CurrentValueSubject<String, Never>("1234") let confirmPasswordPublisher = CurrentValueSubject<String, Never>("1234") var password:String { get { passwordPublisher.value } set { passwordPublisher.value = newValue } } var confirmPassword:String { get { confirmPasswordPublisher.value } set { confirmPasswordPublisher.value = newValue } } var isPasswordValid:Bool { password == confirmPassword && !password.isEmpty } } class Coordinator { var proposal:PasswordProposal var subscription:Cancellable? init() { self.proposal = PasswordProposal() self.subscription = self.proposal.passwordPublisher.sink { [weak self] _ in print(self?.proposal.isPasswordValid ?? "") } } // Simulate changing the password to trigger the publisher. func changePassword() { proposal.password = "7890" } } // -------------------------------- var vc = Coordinator() vc.changePassword() As soon as changePassword() is called, the mutual exclusivity enforcement will throw an exception because the property password will attempt to be read from while it's currently being written to.
Note that if you change this example to use a separate backing storage property instead of the CurrentValueSubject it causes the same exception.
However, if you change PasswordProposal from being a struct to a class, then the exception is no longer thrown.
When I consider how I might use Combine in an existing codebase, as well as in SwiftUI, I see this type of pattern coming up in a lot of places. In the old delegate model, it's quite common for a delegate to query the sending object from within a delegate callback. In Swift 5, I now have to be very careful that none of those callbacks potentially read from the property that initiated the notification.
Have others come across this and, if so, how have you addressed it? Apple has routinely suggested that we should be using structs where it makes sense but perhaps an object that has published properties is one of those areas where it doesn't?