TIL: Swift 4 and protocol composition.

One of the things I like most about Swift is that almost every day I learn something new, more often than not, something new that makes me write more readable and cleaner code.

Today I realised that protocol composition, which I kind of knew could be used to compose a SuperClass and a Protocol, can help remove some downcasts at runtime, and substitute those downcasts by the compiler yelling at you when writing the code.

Let’s take this code. As usual, it is difficult for me to come up with a relevant example that illustrates what I want, but is also simple enough to be understood without too much context.

protocol Presentable {
    func present()
}

class Device {
    func turnOn() {
        print("turn device on")
    }
}

final class Screen: Device, Presentable {
    func present() {
        print("presenting stuff on screen")
    }
}

final class Telegraph: Device {

}

final class Presenter {
    func deliverPresentation(screen: Device) {
        screen.turnOn()
        if let screenPresentable = screen as? Presentable {
            screenPresentable.present()
        }
    }
}

let screen = Screen()
let telegraph = Telegraph()
let presenter = Presenter()
presenter.deliverPresentation(screen: screen)
presenter.deliverPresentation(screen: telegraph)

Notice how I have to check, at runtime, that the parameter I pass to deliverPresentation implements the Presentable protocol.

If we rewrite the Presenter to be like this, we can make the compiler enforce that whatever we pass to deliverPresentation as a parameter also implements Presentable, so we can avoid the downcast.

final class Presenter {
    func deliverPresentation(screen: Device & Presentable) {
        screen.turnOn()
        screen.present()
    }
}

let screen = Screen()
let telegraph = Telegraph()
let presenter = Presenter()

presenter.deliverPresentation(screen: screen)

// The following line will trigger a compiler error
presenter.deliverPresentation(screen: telegraph)

A real life example? From this:

private func showDocumentPicker(origin: UIViewController) {
        let docTypes = [String(kUTTypeImage), String(kUTTypeMovie)]
        let docPicker = UIDocumentPickerViewController(documentTypes: docTypes, in: .import)
        if let delegate = origin as? UIDocumentPickerDelegate {
            docPicker.delegate = delegate
        }
        origin.present(docPicker, animated: true, completion: nil)
}

To this:

private func showDocumentPicker(origin: UIViewController & UIDocumentPickerDelegate) {
        let docTypes = [String(kUTTypeImage), String(kUTTypeMovie)]
        let docPicker = UIDocumentPickerViewController(documentTypes: docTypes, in: .import)
        docPicker.delegate = origin
        origin.present(docPicker, animated: true, completion: nil)
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *