UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to clean up your code formatting with SwiftLint

Get consistent code the easy way

Paul Hudson       @twostraws

Part 3 in a series of tutorials on modern app infrastructure:

  1. How to refactor your code to add tests
  2. How to add CocoaPods to your project
  3. How to clean up your code formatting with SwiftLint
  4. How to streamline your development with Fastlane
  5. How to save and share your work with GitHub
  6. How to validate code changes using CircleCI


There are lots of important and interesting discussions software developers can have: what architecture to use, how to split up your project into sensible components, and what CocoaPods can help you achieve your goals faster, for example.

But there are lots of discussions that aren’t interesting unless you enjoy empty arguments: tabs vs spaces, whether to use Array<Int> or [Int], and which of name: String, name : String, and name :String are correct.

You probably have an opinion on each of those and that’s fine, but does your opinion match those of the rest of your team? How about those of developers who came before you? The answer is “almost certainly not”, which is why we have linters – tools that inspect your code to make sure it matches whatever style guide you have agreed on in your team.

In the Swift world we have SwiftLint, created by serial Swift community contributor JP Simard and others. SwiftLint hooks into Apple’s own SourceKit framework to parse your Swift code, which means it supports the full range of Swift syntax and remains up to date as Swift continues to evolve.

SwiftLint bundles over 75 possible rules as standard, most of which are enabled by default based on general community agree. We’re lucky enough to have extensive Swift style guides available, so SwiftLint does a great job out of the box – and in doing so helps make your code easier to read and maintain.

We’ll look at configuring SwiftLint more soon, but first let’s see what it makes of our test project.

Before we proceed, please download the test app that we’re using for this tutorial series. If you completed the second part of this tutorial series – how to add CocoaPods to your project – you should use the code you had at the end. If you download the original version from GitHub you’ll get slightly different results from me, but that’s OK.

Warning: The example project has been written specifically for this tutorial series, and contains mistakes and problems that we’ll be examining over this tutorial series. If you’re looking for example code to learn from, this is the wrong place.

Next, you need to install SwiftLint. There are a selection of install options, but if you have Homebrew just run this command from your terminal:

brew install swiftlint

See the SwiftLint GitHub repo for alternative instructions if you don’t have Homebrew.

Now change into the directory where you have the Paraphrase project, then change into the Paraphrase subdirectory so you’re in the same place as EditQuoteViewController.swift and other files. Finally, run swiftlint to have SwiftLint examine our code.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

What are the problems?

We just ran SwiftLint inside our project directory, which made it scan only the source code that belonged to our app. Although this might change in the future, for me SwiftLint reported “Found 31 violations, 5 serious in 5 files” – not bad!

Note: If you didn’t follow previous installments in this series, SwiftLint will also have scanned SwiftyBeaver and found more problems.

Most of SwiftLint’s reports are duplicates of the same message, so let’s take a look at the unique errors it has found:

  • Operator Function Whitespace Violation: Operators should be surrounded by a single whitespace when defining them.
  • Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.
  • Vertical Whitespace Violation: Limit vertical whitespace to a single empty line.
  • Trailing Newline Violation: Files should have a single trailing newline.
  • Line Length Violation: Line should be 120 characters or less: currently 144 characters
  • Unused Closure Parameter Violation: Unused parameter "action" in a closure should be replaced with _.
  • Force Try Violation: Force tries should be avoided.

Before each warning, SwiftLint will tell you exactly which file and line number has the problem. In the case of the first warning, “operator function whitespace violation”, you’ll see “Quote.swift:15:12:” – that means Quote.swift line 15 column 12.

Here’s how that line of code looks:

static func <(lhs: Quote, rhs: Quote) -> Bool {

SwiftLint’s warning here is really clear – “operators should be surrounded by a single whitespace when defining them” – so we can fix that line just by adding a single space:

static func < (lhs: Quote, rhs: Quote) -> Bool {

If you save that change, you should be able to run swiftlint to see one fewer violation reported.

Let’s take a look at another easy problem: colon placement. Run swiftlint | grep "colon" to show only the colon violations, and you should see five of them. If you open up the files in question, then look up the appropriate line numbers, you’ll see code like this:

var editingQuote : Quote?

As you can see, I have accidentally-on-purpose placed spaces either side of the colon for the type annotation. Standard practice is to place no space before and one after, like this:

var editingQuote: Quote?

So, go ahead and fix all five colon violations, save the changes, then re-run swiftlint – we should be down to 25 violations now.

Next, try running swiftlint | grep "AppDelegate" to show only errors in AppDelegate.swift. Even though Paraphrase only changes Apple’s original code for this file by a tiny amount, you’ll find there are lots of violations in there – Apple’s own code doesn’t match community guidelines. Yay!

All the violations marked “Vertical Whitespace Violation” are trivial to fix: Apple’s AppDelegate.swift file likes to include lots of extraneous vertical whitespace – i.e., spacing between methods and properties, plus at the start or end of the file – so you just need to go through to remove those.

For example, the AppDelegate class starts with this:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

It’s a bit of a mess, really, but at least it’s easy to fix.

The other violations here are all Line Length Violation, which primarily occur because Apple’s AppDelegate.swift template includes gigantic single-line comments in empty method stubs that most of us ignore.

So, the fix for most of these violations is also easy: select all methods except didFinishLaunchingWithOptions and delete them.

That will remove all but one of the line length violations for AppDelegate.swift, but let’s leave the last one alone for now. Run swiftlint | grep -vi "line length" to show only violations that aren’t about line length, and you’ll see there aren’t many remaining.

In QuotesViewController.swift you’ll see two instances of Unused Closure Parameter. These are caused by the same piece of code in the editActionsForRowAt method:

[unowned self] (action, indexPath) in

Although the indexPath parameter is used, the action parameter is not so there’s no point asking for it. Replace the two instances of that code with the following to fix the violations:

[unowned self] (_, indexPath) in

Another error in QuotesViewController.swift is Trailing Newline Violation, because it has an extra line break at the end – go ahead and remove that.

What remains – apart from the line length warnings we’re ignoring – are three instances of Force Try Violation, all in QuoteModel.swift. Fortunately, none of these are terribly hard to fix.

The first is this:

quotes = try! decoder.decode([Quote].self, from: quoteData)

That line attempts to decode some JSON into an array of quotes. We don’t need the force try there, because if the load fails we can just use an empty array instead, like this:

quotes = (try? decoder.decode([Quote].self, from: quoteData)) ?? [Quote]()

The second is this:

let data = try! encoder.encode(quotes)

There’s no need for a force try there – we can just catch the error and use SwiftyBeaver to log that there was a problem, like this:

do {
    let data = try encoder.encode(quotes)
    defaults.set(data, forKey: "SavedQuotes")
    SwiftyBeaver.info("Quotes saved")
} catch {
    SwiftyBeaver.error("Could not save quotes")
}

And the final force try is this:

quoteData = try! Data(contentsOf: path)

That attempts to read initial-quotes.json from the bundle. Now, in my own code I’d be happy with the force try here because loading a file from your own bundle shouldn’t fail silently, but if you wanted to have a fallback just in case you could ditch the force try like this:

quoteData = (try? Data(contentsOf: path)) ?? Data()

If you run swiftlint one last time you should see all that remains are line length violations – some of our own lines are 154 characters, whereas the built-in rules recommend no more than 120.

While you could fix these, please keep in mind this quote from the creator of SwiftLint, JP Simard:

“Just because a rule exists doesn’t mean you should use it.”

That is, just enabling all the rules and following them all won’t magically make your code better – in fact, it might make it worse.

Customizing SwiftLint

Before we’re done, I want to look briefly at customizing SwiftLint. You see, you might look at the remaining line length violations and think, “as long as lines are under 160 characters I’m happy,” – and fortunately SwiftLint is able to take your preferences into account.

First, run these commands to create an empty file called .swiftlint.yml then open it for editing:

touch .swiftlint.yml
open .swiftlint.yml

Now give it this text:

line_length: 160

.swiftlint.yml is SwiftLint’s configuration file, so if you save that change then run swiftlint again it will only list line length violations if you exceed 160 characters.

In that configuration file you can also disable some rules or enable rules that aren’t enabled by default, like this:

disabled_rules:
    - colon
opt_in_rules:
    - missing_docs

This is particularly useful if you have a large existing project that has no standard style – you can enable only a handful of rules, fix those violations, then enable some more, and fix those, and so on until all the rules you want are enabled.

An alternative to using .swiftlint.yml is to add inline code comments for specific exceptions. This takes three main forms: disabling a rule for whole regions, disabling a rule for the following line, and disabling a rule for the current line.

For example, if you want Swiftlint to ignore one specific force cast, you could write any of these three:

// swiftlint:disable force_cast
let tableViewController = vc as! UITableViewController
// swiftlint:enable force_cast

// swiftlint:disable:next force_cast
let tableViewController = vc as! UITableViewController

let tableViewController = vc as! UITableViewController // swiftlint:disable:this force_cast

All three do the same thing, but be warned: the whole point of using SwiftLint is to add consistency to your code, so if you scatter many exceptions around you’re going in the wrong direction!

Where next?

This is the third part of a short series on upgrading apps to take advantage of modern infrastructure. You’ve seen how easy it is to get your source code to follow a consistent style, but in the following articles we’ll look at other ways computer automation can help us write better code – stay tuned!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.