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,
self
is 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
AnyObject
contraint, 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
mutating
gives 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
Proto2
to conform toAnyObject
-
Approach 2: using
nonmutating
keyword. 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?