Thanks all for the discussion. For people who are still confused by the issue, below is my summary.
1) What this issue is (or: why example 2 and 3 don't work)
First some background:
-
For reference type,
selfis immutable. So it's impossible for an instance method to assign a new instance toself(compiler would throw an error if we did that). -
For value type, however, that is fine. A mutating func can overwrite the entire structure.
The issue arises when we mix class and protocol which has mutating method.
-
When we define a protocol without adding a
AnyObjectcontraint, Swift has no idea that the protocol is for class only. As a result, if the method modifies the instance's members, it has to be defined asmutating.Unfortunately defining a func as
mutatinggives it more power than we want - we may just want to let it modify instance members, but potentially it can modifiyself. So, even the actual func doesn't modifyself, Swift compiler has to prepare for the worst case and assume the func modifiesself. -
That's the reason why the issue arises when we define a class and let the class conform to that protocol. The mutating method defined in the protocol can modify
self! But this shouldn't occur if the instance variable is read-only. This is an unexpected issue because issue like this will never ever occur if we use class without protocol (see item 1 in background).
2) Why example 1 and 4 work
It's because we define Proto2 to conform to AnyObject. I guess that constraint makes the compiler processes the protocol's methods in a similar way as it processes class's methods (for example, throw error if a method tries to overwrite self). Since the compiler has made sure that it's impossible for the methods to overwrite self, it's fine.
3) How to work around it
-
Approach 1: make
Proto2to conform toAnyObject -
Approach 2: using
nonmutatingkeyword. See details in @Jumhyn's answer.
4) More thoughts
In general, what concern me is that if it's good idea to refactor a Swift project in protocol-oriented programming way (note that the project has to use some class types). It feels like that class and protocol don't play well in general and there are a lot of pitfalls. Some are solvable, some are probably not. For example, @jrose made the following comment in this thread:
Even beyond this, mutating and classes don't play so well together. It may not be the best model for what you're trying to do.
I wonder what are people's experience on using class type and protocol-oriented programming in general? Are there any best practice on this?