If you're stuck with the enum, the switch is probably the most convenient method of doing different things for the various cases. if also supports patterns that can match the cases of an enum, but that's rarely more convenient than a switch, and is usually noticeably more awkward, not to mention that if can't enforce that you handled all cases of an enum.
But that doesn't mean you have to litter your code with switch statements. If what you're doing in the various cases is consistent, extract that into a function, for example create an extension on SubSettingsOptionType in which this function would be a method in that extension, and then just use that function instead of a switch every time.
I'll give you an example, but keep in mind I don't really know anything about the context of your code, so the names I give things almost certainly can be much better.
extension SubSettingsOptionType
{
func apply()
{
switch self
{
case .staticCell(let model):
model.isSelected = true
case .switchCell(let model):
model.handler()
}
}
}
With that in place, the code you find inconvenient can be reduced to:
models[0].options[0].apply()
You may have several sets of use cases that come up repeatedly in your code, so you may end up with several such functions, each to handle one of the consistent sets. If you name the function(s) well, your code will be easier to read too.
Another approach might be to imagine how you'd design it in a more convenient way if you were free to replace the enum with something else, then write a set of adapters that wrap the enum, but provide the API you'd prefer.
For example, maybe you'd prefer polymorphism, you might define something like:
protocol CellOptionType {
func apply()
}
struct StaticCellOptionType: CellOptionType
{
var model: SubSettingsOption
func apply() { model.isSelected = true }
}
struct SwitchCellOptionType: CellOptionType
{
var model: SubSettingsSwitchOption
func apply() { model.handler() }
}
extension SubSettingsOptionType
{
var cellOptionType: CellOptionType
{
switch self
{
case .staticCell(let model):
return StaticCellOptionType(model: model)
case .switchCell(let model):
return SwitchCellOptionType(model: model)
}
}
}
Now instead of your original code you can write
models[0].options[0].cellOptionType.apply()
or assign it to a variable to do something with it later
let cellOptionType = models[0].options[0].cellOptionType
// Some time later in your code
cellOptionType.apply()
Is this actually better? Maybe. It depends on how often you need to call apply and how much you store/pass-around SubSettingsOptionType instances.
Yet another alternative is to implement a more fluid-style API in an extension. This can be helpful if you don't have a lot of consistent use cases and the number of enum cases isn't likely to grow. Something like this:
extension SubSettingsOptionType
{
func onStaticCell(_ code: (_ model: SubSettingsOption) throws) rethrows -> Self?
{
switch self
{
case staticCell(let model):
code(model)
return nil
default: return self
}
}
func onSwitchCell(_ code: (_ model: SubSettingsSwitchOption) throws) rethrows -> Self?
{
switch self
{
case switchCell(let model):
code(model)
return nil
default: return self
}
}
}
Then you can use it like this:
models[0].options[0]
.onStaticCell { $0.isSelected = true }
?.onSwitchCell { $0.handler() }
Although I usually like fluid API's, in this case, the need to optionally unwrap here makes it a bit more awkward. There are ways to get around that - wrap the enum, and keep track of which on... methods have been called. That would eliminate the need for the optional. I'll leave that "as an exercise" for now.