2

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?

1 Answer 1

1

The password property is not the problem. It's actually the proposal property. If you add a didSet property observer to proposal, you'll see it's getting reset when you set password, then you access self?.proposal from within your sink while it's being mutated.

I doubt this is the behavior that you want, so it seems to me like the correct solution is to make PasswordProposal a class.

Sign up to request clarification or add additional context in comments.

3 Comments

The problem with didSet is that it's not available on property wrappers since the value property is computed. In a more real-world example, proposal is a wrapped property that exposes a Publisher for interested parties. Ideally, the Publisher would fire in a didSet, but that's not possible. So it has to fire in the set which can then easily trigger the exclusivity exception. (You can put a didSet observer on the non-synthesized property, but then you have to do that for each property, which is nowhere near as convenient as defining it in a reusable property wrapper.)
I'm not suggesting didSet as part of the solution, just as proof of my point, which is that proposal is being reset every time you set password. My solution is to make PasswordProposal a class.
In this simple example, sure. I was just trying to keep the sample code short. In "real life", I have a class that manages things like settings, preferences, configuration options, etc, that has tons of properties. Most of the properties are primitives but some are currently structs. Indeed, converting them to classes solves the problem, as noted above, but I feel that goes against some of Apple's latest recommendations. When I think of all of the different areas I'd love to use Combine for property observation, most of those places use structs.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.