Mixing enums with other Swift types

Tram Ho

One of the most common problems in software engineering in general is the logic that relies on different sources of truth for a given piece of data – especially when those sources may end up contradicting each other. , which tends to lead to an unknown state.

For example, let’s say we are working on an application to write articles, and that we want to use the same data models to represent published articles, as well as projections. Workshop has not been published.

To handle the two cases, we can provide data model data model is isDraft to know whether it is representing a draft, and we will also need to turn any single data to post. Publish newspaper to optionals – like this:

At first, it may not seem like the model above has many sources of truth – but it really doesn’t, because whether an article that needs to be considered for publication can both be determined by looking at the parachute. It has a url assigned to it, or whether isDraft is true.

That may not seem like a big deal, but it can quickly lead to conflicts on our code base, and it also requires unnecessary preparation – like every call site has. both check the isDraft flag, and Unwrap the optional url attribute, in order to ensure that its logic is correct.

This is exactly the kind of situation in which Swift enums really shines – since they let us model types on variations such as state states, each of which can carry its own set of data. in an optional way – like this:

What the enum above allows us to do is replace our previously calculated url and isDraft with a new state attribute – which will act as a unique source of truth to determine the status of each post. :

With the above in place we can now simply turn on our new state attribute whenever we need to check if an article has been published – and need code paths for the article. Publishing is no longer available to deal with any required URLs. For example, here’s how we can now conditionally create a UIActivityViewController for sharing published articles:

However, when making the above types of structural changes to one of our core data models, we will probably also need to update quite a lot of code that uses the model – and We may not be able to make all updates once.

Thankfully, it’s usually relatively easy to solve this kind of problem through some form of temporary backward compatibility layer – which uses our new unique source of truth below, while still letting expose the API just like we had before we went to the rest of the source code.

For example, here’s how we can let its url store until we’re done transferring all our code to its new status API:

So that’s an example of how we can combine structures and other types with enums to establish a unique source of truth for our different states. Next, let’s see how we can go the other way around, and augment some of our enums to make it a lot stronger – while also reducing the overall number of us about conversion reports in the process.

Enums versus protocols

After the above idea of ​​using enums to model different countries – let’s say we are working on a drawing application, and that we have now implemented the tool selection code Our use of an enum contains all drawing tools that support our application:

In addition to the state management aspects, one of the additional benefits of using an enum in this case is the CaseIterable protocol, the type of tool we follow. For example to build a view toolbox that contains buttons for each of our drawing tools:

However, as neat as it is to have all our tools gathered in a single, setup type that doesn’t come with a sizable disadvantage in this case.

Since all our tools will likely need a fair amount of logic, and using an enum requires us to implement all of that logic in a single place, we will probably end up with an increasingly complex series of conversion reports – look for something like this:

One problem with our current approach is that it makes it quite difficult to host country specific stuff – since enums that fit CaseIterable cannot make any value that comes with it.

To solve all of the above two issues, let us instead try to implement each of our tools using a protocol – which will give us a common interface, while still allowing each The tool is declared and implemented in isolation:

However, while the above change allows us to completely separate our various implementations, we also lose one of the main benefits of our enum-based method – we can Easily loop on each tool using Tool.allCases.

But what if we didn’t have to make a choice between protocols and counting, and could instead mix them to sort of achieve the best of both worlds?

Enum externally, protocol inside:

Let’s go back to our Return Type Tool to become an enum, but instead once again implement all our logic like methods and the full properties of conversion reports – Feel free to instead of keeping these protocol-driven implementations, only this time, we’ll make them driven for our tools, rather than the model representation of the tools themselves.

Using our previous protocol as a starting point, let’s define a new protocol called ToolController, – along with our previous request – including a method that allows each tool to provide and manage its own options view. That way, we can end up with a truly discrete architecture, in which each controller completely manages the logic and user interface needed for each given tool:

Going back to our TextTool implementation from before, here’s how we can modify it to instead become a TextToolController that follows our new protocol:

Then, instead of having our enum tool contain any actual logic, we’ll just give it a unique method for creating a ToolController that corresponds to its current state – save them. I had the trouble of writing all the conversion statements we had before, while still allowing us to make the most out of CaseIterable:

Finally, putting all the pieces together, we will now be able to both easily loop on each tool to build a toolbox view and enable the current tool logic by communicating with the ToolController. Its – like this:

Conclude

The beauty of the above method is that it allows us to completely separate logic, while still building a single source of truth for all the states and variants. We may have also chosen to split our code up a bit differently, for example to keep the symbol of each tool and name in our enum, and just move our actual logic out so that ToolController implementations – but that’s always something we can tweak in the future.

The article was translated according to the article of the same name by John Sundell.

Share the news now

Source : Viblo