SwiftLint — Advanced

Rakesh Chander
4 min readOct 23, 2020

--

We all talk to follow Swift Style & Conventions, and we have an impactful tool available for us — SwiftLint. I’ll not talk about more here on how to setup and use this tool in our project. For all these details you can go through their GitHub Page and refer.

I’ll focus here mainly on —

  • Enforce that all default set of SwiftLint rules are marked as errors
  • Adding own custom rules to streamline best practices as per defined organisation level structure
  • Integration to CI

Let’s start with first thing first.

SwiftLint has added various rules, as default, in their own bundle. They have marked few of them as warning, while few are marked as errors. To get list of all — Refer Here

As per me, Swift guidelines must be followed, there is no scope of warnings. If something is not correct then its simply needs to be corrected. For example, SwiftLint has marked “force_unwrapping” as warning in their ruleset while its the most obvious reason of CRASH at run time. It must be corrected. So, here is the script which you can add in your Build Phase, which will mark all warnings as error —

${PODS_ROOT}/SwiftLint/swiftlint lint --strict | sed 's/warning:/error:/g'

We can customise default rule set as per our need in YML file.

Next comes the most beautiful part — Custom Rules

We can add any rule to SwiftLint as per our needs in YML file which will be enforced at build time for the developers. You can refer Custom Rules to get more details. In this story I am going to help you with some rules which I’ve added in our projects and you can use as is OR modify as per your needs.

  • TODO — If there is a TODO declaration in project then that must have git/JIRA link attached to it
  • Multiple Empty Lines — Code File must not have multiple empty lines (It supports Auto Correct)
  • Weak Delegate / DataSource — To prevent retain cycles, delegates & datasources must be declared as weak variables
  • Fatal Errors — Code must not have reference to fatal error calls
  • SwiftLint File Disable — Developers should not be allowed to disable any Swift File lint
  • Try inside Do/Catch — try call must always be in do/catch block
  • Weak Self in Closures — To prevent retain cycle, Closure refering to self, must declare them as weak
  • String Literal — Strings should always be referred from Localization File
  • Protocol Conformance in Extension — Protocol conformance should be declared in separate extensions in the same file
  • UIKit in Model / ViewModel / DataSource — There should be no import of UIKit in these files
  • Public UIModel — UIModel classes must be public
  • Non-Public DataModel & DataSource — DataModel & DataSource must be non-public
custom_todo:
included: ".*.swift"
name: "TODO Violation"
regex: "(TODO).(?!.*(https&)).(?!.*issues)"
match_kinds: comment
message: "TODOs must include a link to the github issue."
severity: warning
multiple_empty_lines:
included: ".*.swift"
name: "Multiple Empty Lines"
regex: '((?:\s*\n){3,})'
message: "There are too many line breaks"
severity: error
non_weak_delegate_datasource:
included: ".*.swift"
name: "Retain Cycle"
regex: '^\ *var\ *(delegate)'
message: "Delegate/datasource may be weak."
severity: warning
no_fatal_errors:
included: ".*.swift"
name: "!!Crash!!"
regex: "fatalError"
message: "Don't use fatalError()"
severity: error
swiftlint_file_disabling:
included: ".*.swift"
name: "SwiftLint File Disabling"
regex: "swiftlint:disable\\s"
match_kinds:
- comment
message: "Prefer swiftlint:disable:next or swiftlint:disable:this"
severity: warning
optional_try:
included: ".*.swift"
name: "Optional Try"
regex: "(try[!,?])"
message: "You should wrap a `try` in a `do/catch` loop rather than forcibly or optionally attempting it."
severity: error
weak_self_closure:
included: ".*.swift"
name: "Use weak self in closures"
message: "Use weak self when create a closure."
regex: "\\{\\s*[^\\[]{0,50}\\s+\\bin\\b([a-zA-Z\\\\.\\(\\)\\[\\{\\}\\>\\/\\=]|\\s)+(self)[^!]
severity: error
multiline_commented_code:
included: ".*.swift"
name: "Commented Code"
regex: '(?<!:|\/)\/\/\h*[a-z.](?!wiftlint)'
match_kinds: comment
message: "Comment starting with lowercase letter - did you forget to delete old code?"
string_literal:
included: ".*.swift"
excluded: ".*(?i)(Constant|Model|DAO)(|s).swift"
name: "String Literal"
regex: '"(.*?)"'
message: "Don't use string lietral directly. Refer them using Constants file."
severity: error
protocol_conformance:
included: ".*.swift"
name: "Protocol Conformance"
message: "Protocol conformance should be declared in separate extensions in the same file"
regex: "(class|struct|extension)[[:space:]]+(?i:(?![^d]*delegate:))[^'\"()<>{},!?:]+:([^'\"<>(){},!?:]+,)+[^'\"<>(){},!?:]*\\{"
match_kinds:
- keyword
severity: warning
public_ui_model:
included: ".*.swift"
name: "Public UIModel"
regex: "^(open |private |internal |)(class|struct) [a-zA-Z]+(?i)UI(Model|DAO)"
message: "UIModel must be public only."
severity: error
non_public_datamodel:
included: ".*.swift"
name: "Non Public Data Model"
regex: "^(open |private |public )(class|struct) [a-zA-Z]+(?i)(Request|Response)(Model|DAO|)"
message: "Data Model must be internal only."
severity: error
non_public_datasource:
included: ".*.swift"
name: "Non Public DataSource"
regex: "^(open |private |public )(class|struct) [a-zA-Z]+(?i)DataSource"
message: "Data Source must be internal only."
severity: error
dont_import_uikit:
included: ".*(?i)(ViewModel|Model|DataSource).swift"
name: "Don't import UIKit in ViewModel"
regex: "^import (UIKit)$"
message: "Don't import UIKit in ViewModel/ Model / DataSource"
severity: error

Integration with CI — GitLab

We can integrate SwiftLint in GitLab Pipeline to make sure that all rules are enforced and corrected in each git push. Refer Continuous Integration — GitLab — iOS — Part 2 for more details on this.

--

--

Rakesh Chander
Rakesh Chander

Written by Rakesh Chander

I believe in modular development practices for better reusability, coding practices, robustness & scaling — inline with automation.

Responses (1)