Protocol Composition an underestimated Swift feature

Photo by Dayne Topkin / Unsplash

Maybe I'm the only one but at first, I didn't use & all that much in the context of protocols in Swift. Now I think it's a real shame because protocol composition makes the code better organized, easier to test and maintain.

Let's say we have feature toggles in our image managing application and based on true/false we enable or disable certain features:

struct ImageApplicationFeatures {
    var isNewImagePreviewScreenEnabled: Bool
    var isMonochromaticEnabled: Bool
    var isSepiaEnabled: Bool
    var isNewHistogramEnabled: Bool
    var isRedifyEnabled: Bool
    var isGreenifyEnabled: Bool
    var isNewFlowEnabled: Bool
}

In our image preview screen, we allow users to use different filters. Some are experimental or are causing problems so we need a mechanism to enable or disable them. Additionally, we just made a new fancy histogram but we need to check it out before shipping to all of the users:

final class ImagePreviewViewController: UIViewController {
    private let features = ImageApplicationFeatures()
    
    private var filters: [Filter] {
                var enabledFilters = [Filter]()
        if features.isMonochromaticEnabled {
            enabledFilters.append(MonochromaticFilter())
        }
        /* here is the code enabling other filters - ommited for simplicyty sake */
        return enabledFilters
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if features.isNewHistogramEnabled {
            presentNewHistogram()
        }
    }
}

The problem here is that when we ask for features we get a list containing all of them which can be a bit intimidating and cumbersome (imagine having 20+ features):

isNewImagePreviewScreenEnabled
isMonochromaticEnabled
isSepiaEnabled
isRedifyEnabled
isGreenifyEnabled
isNewFlowEnabled

One way of dealing with this is to create a separate protocol i.e. ImagePreviewFeatures that will contain only features needed for this screen which is enabled filters and information of whether to show or not the new histogram. It's totally doable option and definitely much better than what we have now.

The problematic part is that each screen would require a separate protocol. There is a high chance that a bunch of the features will be needed in more than one screen therefore in this approach we may end up having multiple more or less similar protocols. Still, it's better than the original approach.

What I would like to propose instead is to use the & I mentioned in the first sentence and see what happens. But before we can do that we need to refactor the way we provide feature toggle information. First, let's add a protocol for each feature:

protocol NewImagePreviewScreenFeature {
    var isNewImagePreviewScreenEnabled: Bool { get }
}

protocol MonochromaticFeature {
    var isMonochromaticEnabled: Bool { get }
}

protocol SepiaFeature {
    var isSepiaEnabled: Bool { get }
}

protocol RedifyFeature {
    var isRedifyEnabled: Bool { get }
}

protocol GreenifyFeature {
    var isGreenifyEnabled: Bool { get }
}

protocol NewHistogramFeature {
    var isNewHistogramEnabled: Bool { get }
}

protocol NewFlowFeature {
    var isNewFlowEnabled: Bool { get }
}

Then ImageApplicationFeatures needs to implement all of them:

struct ImageApplicationFeatures: NewImagePreviewScreenFeature,
                                 MonochromaticFeature,
                                 SepiaFeature,
                                 RedifyFeature,
                                 GreenifyFeature,
                                 NewHistogramFeature,
                                 NewFlowFeature {

When this is done instead of:

private let features = ImageApplicationFeatures()

We can do:

private let features: MonochromaticFeature & SepiaFeature & RedifyFeature & GreenifyFeature & NewHistogramFeature = ImageApplicationFeatures()

If we want to be more strict about it we can add a type alias:

private typealias ImagePreviewFeatures = MonochromaticFeature & SepiaFeature & RedifyFeature & GreenifyFeature & NewHistogramFeature

private let features: ImagePreviewFeatures = ImageApplicationFeatures()

With this approach features variable contains only features needed for this screen and there is no need for creating a separate protocol for each screen. Instead, we create a protocol for each feature so later we can compose these protocols in any way we see fit.

Thank you for reading!

Kamil Tustanowski

Kamil Tustanowski

I'm an iOS developer dinosaur who remembers times when Objective-C was "the only way", we did memory management by hand and whole iPhones were smaller than screens in current models.
Poland