It is largely accepted in the Swift community that the type system reduces bugs. There are trivial examples of the compiler catching mistakes such as passing the wrong numeric type to function. This class of bug is found with no help needed from developer. True enough. But when the developer does make the effort to carefully design types, then the more profound benefits of strong, static typing become available.
A Naive Approach
Here’s one way to represent the state of a network connection.
ConnectionState is a simple enum with three possible cases. The
ConnectionInfo struct provides information about different aspects of a connection. The
state and the
server fields are required, the rest are optional. The optional fields are not populated willy-nilly: they operate under strict invariants:
lastPingIdare used to keep the connection alive. They are optional because they should only be present when
connected, and in that case both must be present.
sessionIdis a unique identifier for each new connection and it should only be present when
initiatedis a timestamp used to determine whether the attempt to connect should continue. It should only be present when
disconnectedis a timestamp but is used to record when the connect disconnected. It should only be present when
There are numerous ways this code can break. Preventing that requires precise documentation. Each client must be subjected to complex tests to confirm it is using this code correctly. Therefore, it requires continuous vigilance on the part of developers to maintain these invariants.
A Better Solution
What we want is to be able to write the code once and forget it, confident that it works and cannot be abused. That’s the dream anyway, and we can get close if we think carefully about our types. As it turns out, these invariants can be baked directly into the code. Then, you don’t need to document how the code works, that would be like explaining how Swift itself works. It doesn’t need complex tests, because you don’t need to test Swift.
ConnectionInfo has simplified down to its bare requirements: it must have a
state and an
server address. The previous optional fields have been moved to associated types on their appropriate cases.
- When the
connecting, there must be a date stored right there in the case.
- When the
connected, there must be a
sessionIdinstance available just the to
connectedinstance. There may be an instance of
Pingas well, only if any pings were sent. But you know that
lastPing, if it is not nil, will have both a
disconnected, you can depend on the
disconnectedtimestamp to be available.
Now that the invariants are part of the types themselves, the compiler can detect and reject code that violates these invariants. This is less work and more reliable than trying to maintain these invariants by hand. Both examples are valid Swift, but the second is superior because it shifts more overhead from the developer to the language.
Encoding and Decoding Enums with Associated Values in Swift 4
How to update your Cocoapod