SwiftLint

The folks at Realm recently released the first comprehensive linter for swift, known by the moniker SwiftLint. SwiftLint is based loosely on GitHub’s Swift Style guide.

Swiftlint combs through your codebase at build time, raising warnings if any of its rules are violated. Here’s a complete list of the rules:

  1. Colons:
    • Type declarations must be preceded by a colon, like so: let matt: Person
  2. File Length
    • No longer than 400 lines
  3. Force Casting
    • Force casts, such as person as! Person, are not allowed. this one led me to embrace nil coalescing as a way to get around the verbose if let pattern. swift has a shorthand operator, ?? which is shorthand for a != nil ? a! : b
  4. Function Length
    • No longer than 40 lines
  5. Leading White Space
    • Exactly what you might think
  6. Trailing White Space
    • Same here
  7. Trailing New Line
    • And here
  8. Line Length
    • Individual lines must be no longer than 100 lines. This one gave me a bit of trouble, given the lengthy nature of many Cocoa class names. Getting around it meant currying where possible (structuring classes around small functions that take limited arguments), and being economical when naming variables.
  9. TODOs
    • No TODOs
  10. Type Names
    • Must be alphanumeric and between 3 and 40 characters
  11. Type Body Length
    • No longer than 40 lines
  12. Variable Names
    • Must be alphanumeric and between 3 and 40 characters

New Shiny Code

The most visible benefit of running SwiftLint and making the necessary changes was syntactical. Now my spacing is consistent, my function signatures are compact; my code is, aesthetically speaking, easier to spend time with and maintain.

Nil Coalescing

To mitigate the force unwrapping rule, I made use of Swift’s handy nil coalescing operator. What’s that? Well, to give you some background, oftentimes when you are taking a first pass at a feature, it is convenient to “force unwrap” an optional like so: let matt: Person = optionalMatt as! Person. This can easily cause your program to crash if optionalMatt is nil. Crashes like this can be avoided using the nil coalescing syntax below

let matt: Person = optionalMatt as? Person ?? Person()

which sets matt to optionalMatt if matt is not nil, and otherwise generates a new Person instance.

View Models

SwiftLint’s type body length rule warned me that some of my view controllers were a little on the heavy side. To address this issue, I took as much model processing logic as I could out of my view controllers and split them out across a number of view models.

In one case, I had a UIView dedicated to a slider control. Initially in my view controller, I called a configuration method in viewDidLoad that set a number of properties on it:

class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureSliderView()
    }
    
    private func configureSliderView() -> {
        self.sliderView.backgroundColor = topBarBackgroundColor
        self.sliderView.layer.shadowColor = UIColor.blackColor().CGColor
        self.sliderView.layer.shadowOffset = CGSizeMake(0, 5)
        self.sliderView.layer.shadowOpacity = 0.15
        self.sliderView.layer.shadowRadius = 1.0
    }
    
}

This approach to configuring a subview is fine, but if you have a view controller with a number of subViews, the lines start to pile up. One effective solution to the problem of class length is to create a separate view model class, a flexible view configuration class, and pass subviews, with all of their logic, from the view controller to the view model:

class ViewModel: NSObject {
  var sliderViewConfigurationObject: ViewConfigurationObject
  
    var sliderView: UIView? {
      didSet {
            if let unwrappedSliderView = sliderView {
                sliderViewConfigurationObject = unwrappedSliderView
            }
        }
    }
}

class ViewConfigurationObject: NSObject {

    var backgroundColor: UIColor?
    var shadowColor: CGColor?
    var shadowOffset: CGSize?
    var shadowOpacity: Float?
    var shadowRadius: CGFloat?

    var view: UIView? {
        didSet {
            if let unwrappedView = view {
                unwrappedView.backgroundColor = backgroundColor ?? unwrappedView.backgroundColor
                unwrappedView.layer.shadowColor = shadowColor ?? unwrappedView.layer.shadowColor
                unwrappedView.layer.shadowOpacity = shadowOpacity ?? unwrappedView.layer.shadowOpacity
                unwrappedView.layer.shadowRadius = shadowRadius ?? unwrappedView.layer.shadowRadius
            }
        }
    }
}

With the ViewModel and ViewConfigurationObject classes in place, the view controller’s role is simple. All it has to do is instantiate a ViewModel:

class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        viewModel = ViewModel()
    }
}
  

Moving my model processing logic into the view model layer drastically reduced the size of my view controllers, and produced smaller classes with well-defined responsibilities. Hooray SwiftLint.

Some Parting Feelings

All in all, I recommend making SwiftLint part of your workflow. Although I might drop a rule or two (not sure if clarity of intent was sacrificed in order to meet the line length requirement when I refactored my function declarations), on the whole you’ll come away with a cleaner codebase and a better understanding of your original design. The roadmap for SwiftLint includes a higher degree of configurability, so odds are good that we’ll be able to toggle individual rules on/off. For the time being at least, I plan on sticking with it.