Custom Operators in Swift with considerations for readability

Custom operators in Swift create extensions on existing types and can be great for readability improvements if used correctly.

Considerations with custom operators

For code solutions in your project, you’re probably more likely to define a custom method instead of a custom operator. With the right knowledge and considerations, you can easily create more readable code with custom operators.

Try to create extensions with known operators

Use the same operators as existing in the default Swift API like +, - and * to make your custom operators easier to understand by fellow developers.

Don’t introduce an operator for one-time usage

If the operator is only used in one place you’re more likely to introduce extra overhead than improved readability

Readability improves when often used

If the operator is potentially often used in small pieces of code you can improve readability a lot. Examples of small pieces of code are calculations or applying offsets or insets.

Examples given and compared

Consider the example in which we try to extend a default size with an offset based on a border width.

The properties

let defaultCellSize = CGSize(width: 25, height: 25)
let outerBorderWidth: CGFloat = 3

Using an inline solution

let totalCellSize = CGSize(width: defaultCellSize.width + outerBorderWidth, height: defaultCellSize.height + outerBorderWidth)

Using a custom method

func apply(offset: CGFloat, to size: CGSize) -> CGSize {
return CGSize(width: size.width + offset, height: size.height + offset)
}

let methodTotalSize = apply(offset: outerBorderWidth, to: defaultCellSize)

Using a custom extension

extension CGSize {
func with(offset: CGFloat) -> CGSize {
return CGSize(width: width + offset, height: height + offset)
}
}

let extensionTotalSize = defaultCellSize.with(offset: outerBorderWidth)

Using a custom operator

extension CGSize {
static func + (size: CGSize, offset: CGFloat) -> CGSize {
return CGSize(width: size.width + offset, height: size.height + offset)
}
}

let operatorTotalSize = defaultCellSize + outerBorderWidth

Which solution is the best?

It totally depends on your taste, but comparing the different solutions in a row can give you the best idea on readability.

let totalCellSize = CGSize(width: defaultCellSize.width + outerBorderWidth, height: defaultCellSize.height + outerBorderWidth)
let methodTotalSize = apply(offset: outerBorderWidth, to: defaultCellSize)
let extensionTotalSize = defaultCellSize.with(offset: outerBorderWidth)
let operatorTotalSize = defaultCellSize + outerBorderWidth

Taking the earlier named considerations into account would lead us at WeTransfer to go for the extension solution:

let extensionTotalSize = defaultCellSize.with(offset: outerBorderWidth)

We do this for the following reasons:

  • It’s discoverable from autocompletion on a size object
  • Reading the method explains what it does
  • The above reasons make the solution easier to adopt by any new developers on the project.

Defining your custom operator

As shown in the above example it’s easy to define your own custom operator. Create a custom extension on a type with as input the type you extend and any other type.

In the following example, we’re extending CGSize to receive a CGFloat offset.

extension CGSize {
static func + (size: CGSize, offset: CGFloat) -> CGSize {
return CGSize(width: size.width + offset, height: size.height + offset)
}
}

Originally published at SwiftLee.

More posts and updates: @twannl