Why protocol is better than class in swift?

class and protocol are orthogonal concepts. A protocol cuts across the class tree and joins one or more classes with a disparate ancestry.

Perhaps put more simply:

  • "class" defines what an object is.
  • "protocol" defines a behavior the object has.

So you have a class Car:

class Car {
    var bodyStyle : String
}

and a class Color:

class Color {
    var red : Int
    var green : Int
    var blue : Int
}

Now, more or less obviously Colors and Cars are completely unrelated, however, let's suppose I want to be able to easily convert either one to Strings, so I can debug with:

print(Car(...))

or

print(Color(...))

For exactly this purpose, the Swift language defines the protocol CustomStringConvertible so we can then declare the a Car can be printed by using that protocol:

extension Car : CustomStringConvertible {
    var description : String { get { return "Car: \(bodyStyle)" } }
}

and a Color as well:

extension Color : CustomStringConvertible {
    var description : String { get { return "Color: \(red) \(green) \(blue)" } }
}

So where before I would've needed one print method for each class, now I only need one print method that looks something like:

func print(data:CustomStringConvertible) {
    let string = data.description
    ... bunch of code to actually print the line
}

This is possible because declaring that a class implements a protocol is a promise that I can use the methods from the protocol, knowing that they're implemented and (presumably) do what's expected.


Lets take a downloading example.

You have a Base class FileDownloadModel, and have 3 subclasses AudioFileDownloadModel, VideoFileDownloadModel, and ImageDownloadModel.

You have a DownloadManager that takes a FileDownloadModel input and uses this model's urlToDownload property to download the file.

Later down the line you are told that there is one more model coming but it's of type UserDownloadModel which is a subclass of User, and not FileDownloadModel.

See now it becomes difficult to handle this scenario where you will have to change a lot of code to incorporate downloading methods.

How protocol oriented programming will help you here:

  1. Create a protocol named DownloadingFileProtocol and add methods and properties that you need for downloading a file. eg. urlToDownload, pathToSave, extension etc.
  2. Implement the same protocol in FileDownloadModel and UserDownloadModel. The benefit here is that you don't have to change a lot of code in UserDownloadModel. You will just implement the methods from the DownloadingFileProtocol.
  3. If a new entity comes down the line again, you will not change any code. Rather, you'll just implement the protocol methods.
  4. And now your DownloadManager can take a DownloadingFileProtocol as input instead of a specific model. As well, you can now make any model "downloadable" by having it adopt this protocol.

With protocols, one class/struct can be used as different things. For example, the String struct conforms to soooo many protocols!

Comparable
CustomDebugStringConvertible
Equatable
ExtendedGraphemeClusterLiteralConvertible
Hashable
MirrorPathType
OutputStreamType
Streamable
StringInterpolationConvertible
StringLiteralConvertible
UnicodeScalarLiteralConvertible

This means that String can be used as 11 different things! When a method needs any one of the above protocols as a parameter, you can pass in a string.

"But I can just create a god class that has all of the methods that the protocols have!" you argued. Remember, you can only inherit from only one class in Swift, and multiple inheritance is just so dangerous to use that it can make your code super complex.

Also, with protocols, you can define your custom behaviour of the class. String's hashcode method is not the same as that of Int. But they are both compatible with this function:

func doStuff(x: Hashable) {

}

That is the true wonder of protocols.

Last but not least, protocols make sense. Protocols represent an "is a kind of" or "can be used as" relationship. When X can be used as Y, it makes sense that X conforms to protocol Y, right?


Largely, it's hierarchy of types. Lets say you have an object representing a GlowingRedCube, but you want to have that type used in lots of generic code that cares about:

  • Different Shapes - Cube extends Shape
  • Different Colors - Red extends Colorful
  • Different type of illumination - Glowing extends Illuminated

You're in trouble. You could chose a base class and add specialisations: GlowingRedCube extends GlowingCube extends Shape, but then you get an very wide set of classes, and an inflexible set of things (what if you wanted to make a SoftRedCube, but keep any methods you've defined for your existing type of red cube?)

You could just have Cube and have illumination and shape be properties, but then you don't get nice compiler type checking: if you have a Room.lightUp() method and have to pass it a Cube, you then need to check whether that type includes any illumination! If you could only pass it an Illuminated then the compiler would stop you as soon as you tried.

Protocols allow you to separate this: GlowingRedCube can implement the Illuminated protocol, the Colorful protocol and the Shape protocol. Because of protocol extensions, you can include default implementations of functionality, so you don't have to choose a level of hierarchy to attach it to.

struct GlowingRedCube: Shape, Colorful, Illuminated {
 // ..
}

Effectively, protocols allow you to attach behavior to an object, regardless of what else that object does. That's exactly why they're used for things like delegate and datasource protocols: even if you're mostly attaching those things to a ViewController, the underlying object isn't relevant, so you can be flexible about how you implement.

There's a lot more to using protocols in Swift than just the basics though: they are exceptionally powerful because they can be attached to a number of different code constructs: classes, structs and enums. This allows you to really approach programming protocol first. There's a great video on this approach from WWDC last year, but it helps to have spent some time trying some different object structures yourself first to get a feel for the problems.