How Senior iOS Engineers Style Code: Performance And Clarity

Daniel James
5 min readJan 1, 2023

--

This article is the result of studying Swift Style from The Pragmatic Bookshelf and lived experience. Any level iOS engineer can use this guide.

You Had No Concept Of Styling Code

when you first started writing software. Likely, a successful compilation was fantastic. However, as the years go by, developing a code styling framework is necessary for utility and signifies expertise. Some of these tips are driven by performance enhancements and others by pure visual style.

Styling braces in Swift

is straightforward. If you want to woo your coworkers then share these nerdy bits…

There are 2 styles of formatting braces. The first, One True Brace Style, pompous, I know, is known as 1TBS.

1TBS Style

If let x = x {
// code
} else {
// code
}

The second is the Allman Style, also known as BSD style.

Allman

If let x = x 
{
// code
}
else
{
// code
}

Unbeknownst to many Swift coders, we overwhelming follow the 1TBS style. However, the Allman style can come in handy for situations for which you need better readability. You can use a hybrid approach of both on a singular scope.

Avoid Eager Evaluations With The Lazy Keyword

when using high order functions. Lazy, of course, will delay the evaluation until the higher order function is needed. This will save you from creating arrays unnecessarily.

let newCars = allCars.lazy.filter({$0.isNew})

Complex Guard Statements In Swift

should be remanded to individual guards and not listed in groups.

func validateScore() {

//Prefer
guard let score = score else {return}
guard score.isEmpty else {return}
guard let validator = validator else {return}


//Avoid
guard let score = score,
score.isEmpty,
let validator, validator
else {return}
}

This styling allows you to easily comment individual guard statements and adjust the vertical spacing for comments.

Coalesce and Ternary operators

can find themselves relevant in similar scenarios. Swift Style recommends prioritizing coalescing for its shortness and clarity.

//Prefer
backgroundColor ?? .grey

//Avoid
backgroundColor == nil ? .grey : backgroundColor!

When using a ternary operator stack the “?” and “:” vertically like this…

backgroundColor == nil 
? .grey
: backgroundColor!

In my experience, your mentor, coworkers and future self will applaud this style of formatting because commits that change the values will only affect one line. If I change .grey to .white it will be clearly visible in the PR.

Styling Collections

is straight forward, give each element a singular line and leave a trailing bracket. This, again, aids in writing clean PRs.

// Prefer
let carColors: [String: UIColor] =
[
"Corvette": .yellow,
"Tesla Model Y": .black,
"Toyota Tundra": .blue
]

//Avoid
let carColors: [String:UIColor] = ["Corvette": .yellow,
"Tesla Model Y": .black,
"Toyota Tundra": .blue]

//Short collections like this are okay
let nascarNumbers: [Int] = [1, 23, 44, 62]

When Adding Collections

prefer append(contentsOf:) and append() to “+”. “+=” calls append(contentsOf:).

//Prefer 
cars.append(contentsOf: mustangs)

//Prefer
cars += mustangs

//Avoid
cars + mustangs

Faster, Cleaner Iteration Of Optionals

can easily be accomplished with case let pattern matching. Both of these patterns do the same thing except case let is much cleaner.

//Prefer
for case let pet? in pets {
}

//Avoid
for pet in pets {
if let pet = pet {
}
}

Be More Descriptive With forEach

or utilize a for in loop to be more descriptive.

//Prefer
for car in cars {print(car)}

//Prefer
cars.forEach {car in access(car)}

//Avoid
cars.forEach {access{$0}}

Integer Readability

can be made simpler with this underscore tactic.

//Prefer
let usBankruptValue: Int = 7_000_000

//Avoid
let usBankruptValue: Int = 7000000
//What number is this ^ you mean I have to count the zeros now?

Swift ignores the underscore during compilation. Stick to the natural pattern denoting advancements of 3 places.

Type Declarations

Should be consistent and we should avoid relying on type inference. Be as descriptive as possible with values because 20 could be an integer or double but 20.0 can not be an integer.

//Prefer
let leadingConstant: CGFloat = 20.0

//Avoid
let leadingConstant: CGFloat = 20

//Avoid
let leadingConstant = 20.0 as CGFloat

Stick to consistent type declarations and, if your company style guide permits, prefer type declarations before the “ = ” sign.

//Prefer 
let cars: [String] = []

//Prefer Repeating When Possible
let cars: [String] = .init(repeating: "Chevy", 6)

//Avoid
let cars = [String]()

Prefer Casting Or Literals

to avoid extra function calls. In the following avoid example we are using the UInt initializer needlessly.

//Prefer
let carNumber: UInt8 = 8

//Avoid
let carNumber = UInt(8)

With OptionSets

be as descriptive as possible with with the arguments. A great example from Swift Style is…

//Prefer
accessQueue.sync(flags: [.barrier])

//Avoid
accessQueue.sync(flags: .barrier)

Let the brackets communicate that idea that more options can be added.

When Working With Optionals

you can use the syntactic sugar of Swift to provide clearer pattern matching.

//Prefer
switch (optionalOne, optionalTwo) {
case let (first?, second?):...
}

//Avoid
switch (optionalOne, optionalTwo) {
case let (.some(first), .some(second):...
}

Cleaner Tuple Declarations

can shorten blocks of code vertically and allow you to declare related items in the same location..

//Maybe use this
var (carId, mpg) = (1395, 32)

//Maybe use this
var carId = 1395
var mpg = 32

//Avoid
var carId = 1395; var mpg = 32

Swapping with tuples can be accomplished faster than the classic way of establishing a temporary value…

//Prefer
(x, y) = (y, x)

//Prefer - write a function to swap
swap(&first, &second)

//Avoid
let tmp = second
second = first
first = tmp

Use Same Name Shadowing

when unwrapping optionals…

//Prefer
guard let car = car else {return}

//Avoid
guard let mustang = car else {return}

This can help you avoid naming collisions outside of scope and minimize the need to use self.

You can also unwrap using the var keyword…

//Prefer, mutable car
if var car = car {}

//Avoid
if let car = car {

//Making car mutable
var variableCar = car
}

Use The “Is” Keyword

to check if type-erased keywords can be resolved as another type. This can be useful if you have no need to perform a cast.


if object is car {
print("")
}

Try?

should be reserved for times when you don’t care why the operation is failing. Instead, use the do-catch pattern.

//Prefer
do { try requestCars() }
catch { assertionFailure("Request Cars Failed") }

//Avoid
let cars = try? requestCars()

When Only Needing Indices

you can loop through them directly without enumerating.


//Prefer
for carIdx in cars.indices {
}

//Avoid
for (index, _) in cars.enumerated {
}

Upgrade Your Switch Statements

with where clauses. Also, align case keywords vertically with the switch keyword.

switch car.isBlack() {
//If car is available do something
case true where car.isAvailable(): ...
//If true and unavailable do something else
case true: ...
default:...
}

Prefer Doubles To Floats

as doubles are more precise. A double holds 15 decimal digits and and float has a precision of 9.

Prefer Int to UInt (or other variations)

Int supports better interoperability. On a 32 bit system the Int is 32 bits. On a 64 bit system, the integer is 64 bits. This is not the case for an Int32 or Int64 which is fixed size.

Implicit Self Should Be Leveraged

whenever possible for brevity. Avoid explicitly calling self when unnecessary.

struct Car {
var color: UIColor? = .black

mutating func changeColor() {

//Prefer
color = .white

//Avoid
self.color = .white
}
}

Prefer Weak Capture To Unowned

because weak is safer, unowned is meant for objects with a guaranteed lifetime.

--

--

Daniel James

Senior Developer based in Austin, TX. I share my computer science study with the world here.