There is a post about using structs or classes in Swift here: Should I use a Swift struct or a class?. If you like functional programming or reducing state mutations, be warned, it's somewhat antagonistic to your position.
The fundamental problem I find with the piece is that it creates a false selection of choices to the problem statement posed while misrepresenting much of the advice about chosing when to use value types. I think it also confuses the purpose of the proposed "inner class device".
1. We have a protocol defined as:
protocol CanSend {
typealias Message
func send(messgage: Message) throws
}
2. We need to implement this protocol in two manners: one that mutates and one that does not.
Specifically, the desire is to create a MockServer<T>
that can be used to keep track of state for testing. Ok, this sounds reasonable. So the following is presented:
struct MockSender<T> {
var sentMessages: [T] = [] //you inspect this property to study
//what messages were previously sent for unit test purposes
}
extension MockSender : CanSend {
typealias Message = T
func send(message: T) {
self.sentMessages.append(message) //error: Immutable value
//of type '[T]' only has mutating members named append
}
}
This, as pointed out, does in fact present a problem. The protocol function send
is not marked as mutating
yet the implementation clearly attempts to mutate itself.
However, this is the conclusion that is drawn:
Right, so the problem is our protocol says the function is non-mutating, but in reality it needs to be mutating.
So we go change the protocol, right? I mean after all, by definition this program has no legacy code, so we can structure the protocol however we want.
After some clammering about how this is the wrong approach, which I agree, there is no need to mark the function as mutating
, we get to this conclusion:
We can solve this problem with a device called an "inner class", which you probably have already seen, but if you haven't just imagine that there is some god-awful way to create a mutating implementation of a non-mutating func, and we will cover the details later.
This, is actually the wrong conclusion. The entire purpose of the "inner class" is to provide value semantics while maintaining effeciency of implementation. In addition to that, if some of the quoted advice in the article had been followed with regards to struct usage, a different conclusion would have presented itself. Let's look at this piece of advice:
Don't use classes when structs will do. Use classes if you want reference types. Use structs if you want value types. You can add functionality to both (and to enumerations) in Swift. When in doubt, err on the side of value types. If your construct doesn't have a life cycle, or two constructs with similar values should be considered as the same thing, use a struct. (Do take advantage of Swift structs. They're neat.)
I emphasized the interesting bit from there.
You see, MockSender<T>
does indeed have a lifecycle; it has a history of all messages sent that varies over time.
Also, this piece of advice:
Unless you require functionality that can only be provided by a class (like identity or deinitializers), implement a struct instead… Rationale: Value types are simpler, easier to reason about, and behave as expected with the let keyword.
In addition to that, we have a protocol in which we do not want to enforce a mutation contract across all conforming types. A class provides us the functionality that only it provides. So why not simply follow the advice and create this version instead?
{} class MockSender
var sentMessages: [T] = []
}
extension MockSender : CanSend
typealias Message = T
func send(message: T) {
self.sentMessages.append(message)
}
}
This solution does exactly what we want. It also does it by maintaining the protocol can be conformed by immutable value-types and my mutable reference types. This is a validation of the "Structs Philosophy‚Ñ¢"; `MockServer<T> is not a value-type, don't try and make it one.
The problem with the first example is that, in my opinion, the author conflated two separate things: the desire to create a value type and the usage of inner classes in value types for performance reasons. If, we really wanted immutable value-types from the protocol and allow state changes, the protocol should have been defined as such:
protocol CanSend
typealias Message
func send(messgage: Message) throws -> Self
}
The weird part about the piece is that we end with the conclusion that we should have just started with, and that conclusion isn't in disagreement to much of the advice out there about starting with a struct and move to a class when/if necessary:
> In all other cases, define a class, and create instances of that class to be managed and passed by reference. In practice, this means that most custom data constructs should be classes, not structures.
So...
- Yes, make your `MockServer<T>` a class.
- No, do **not** write the "struct with inner class wrapper" so you can simply make it pseudo-mutable.
- You should still start with the question: is my type a value-type or a reference type? This is also the advice from Apple.
**Update Sunday, July 5th @ 4:03PM**
Here's a follow-up [conversation thread](https://gist.github.com/drewcrawford/752b2bfef7d8574eab01).