STYLEGUIDE.md

February 18, 2022 · View on GitHub

Goals

Following this style guide should:

  • Make it easier to read and begin understanding unfamiliar code.
  • Make code easier to maintain.
  • Reduce simple programmer errors.
  • Reduce cognitive load while coding.
  • Keep discussions on diffs focused on the code's logic rather than its style.

Note that brevity is not a primary goal. Code should be made more concise only if other good code qualities (such as readability, simplicity, and clarity) remain equal or are improved.

Guiding Tenets

  • The official Swift API design guidelines are all unilaterally accepted for any public API and generally have good information for private or internal APIs. If you are not making a public API we do not require the same level of documentation, but we still recommend that you adhere to the naming conventions and general design guidelines.
  • These rules should not fight Xcode's ^ + I indentation behavior.
  • We strive to make rules lintable:
    • If a rule changes the format of the code, it needs to be able to be reformatted automatically using SwiftLint.
    • For rules that don't directly change the format of the code, we should have a lint rule that throws a warning.
    • For rules that cannot be handled directly with SwiftLint we will strive to have our own linter (for example, file names).

How to read the guides

Each guide is broken into a few sections. Sections contain a list of guidelines. Each guideline starts with one of these words:

  • DO guidelines describe practices that should always be followed. There will almost never be a valid reason to stray from them.
  • DON’T guidelines are the converse: things that are almost never a good idea.
  • PREFER guidelines are practices that you should follow. However, there may be circumstances where it makes sense to do otherwise. Just make sure you understand the full implications of ignoring the guideline when you do.
  • AVOID guidelines are the dual to “prefer”: stuff you shouldn’t do but where there may be good reasons to on rare occasions.
  • CONSIDER guidelines are practices that you might or might not want to follow, depending on circumstances, precedents, and your own preference.

Table of Contents

  1. Xcode Formatting
  2. Naming
  3. Style
    1. Functions
    2. Closures
    3. Operators
    4. Enumerations
    5. Generics
  4. Patterns
    1. Initializers
    2. Method Complexity
    3. Control Flow
    4. Access Control
    5. Optionals
    6. Immutability
    7. Protocols
    8. Type Erasure
  5. File Organization
  6. Objective-C Interoperability

Xcode Formatting

  • DO Trim trailing whitespace in all lines.

  • DO Indent Case Statements in a Switch.

    Why?

    We feel this greatly increases readability. We find it a little surprising that isn't the default.

    How?

    Under Xcode -> Preferences -> Text Editing -> Indentation you can tell Xcode to indent case statements in Swift.

  • DO use 4 spaces over tabs. This is the Xcode default, and not something we will change.

  • DON'T use #imageLiteral or #colorLiteral (don't drag colors or images from xcode into code).

  • AVOID leaving compiler warnings unattended.

    Every once in a while there's simply nothing reasonable you can do, because a generated file causes warnings. You also may deliberately use #warning("") in your code to draw attention to something.

⬆ back to top

Naming

  • PREFER deprecation over removal for public APIs.

    Why?

    We want to give consumers of our code the opportunity to adapt to changes, outright removal means we'll constantly be breaking down-stream teams and that's a good way to frustrate developers.

    We will do our best to support older methods, but we will also not hesitate to do something new and better if there's a new and better way of doing things. This is the same approach Apple tends to take.

  • DO name booleans like isSpaceship, hasSpacesuit, areTermsAccepted etc. This makes it clear that they are booleans and not other types.

  • DO name event-handling like past-tense sentences. The subject can be omitted if it's not needed for clarity.

    // WRONG
    class ExperiencesViewController {
    
      private func handleBookButtonTap() {
        // ...
      }
    
      private func modelChanged() {
        // ...
      }
    }
    
    // RIGHT
    class ExperiencesViewController {
    
      private func didTapBookButton() {
        // ...
      }
    
      private func modelDidChange() {
        // ...
      }
    }
    
  • AVOID *Controller in names of classes that aren't view controllers.

    Why?

    Controller is an overloaded suffix that doesn't provide information about the responsibilities of the class.

  • PREFER prefixing types with modules over creating wrongly named classes.

    // WRONG
    // in module 1
    struct Appointment {
      var time: Date
    }
    
    // in module2
    struct OtherAppointment {
      var time: Date
      var reason: String
    }
    
    // in calling code
    Appointment(time: Date())
    OtherAppointment(time: Date(), reason: "sick")
    
    // RIGHT
    // in module 1
    struct Appointment {
      var time: Date
    }
    
    // in module2
    struct Appointment {
      var time: Date
      var reason: String
    }
    
    // in calling code
    Module1.Appointment(time: Date())
    Module2.Appointment(time: Date(), reason: "sick")
    
  • DO use US english spellings of words in code. This is to fit with the convention of english spellings that is already part of the swift standard libraries

  • PREFER short-hand type names.

    // WRONG
    let x = Dictionary<AnyHashable, Any>()
    let y = Array<String>()
    
    // RIGHT
    let x = [AnyHashable: Any]()
    let y = [String]()
    
  • PREFER delegate naming conventions similar to the Cocoa's.

    Methods on delegate protocols and delegate-like protocols (such as data sources) are named using the linguistic syntax described below, which is inspired by Cocoa’s protocols.

    The term “delegate’s source object” refers to the object that invokes methods on the delegate. For example, a UITableView is the source object that invokes methods on the UITableViewDelegate that is set as the view’s delegate property.

    All methods take the delegate’s source object as the first argument.

    For methods that take the delegate’s source object as their only argument:

    If the method returns Void (such as those used to notify the delegate that an event has occurred), then the method’s base name is the delegate’s source type followed by an indicative verb phrase describing the event. The argument is unlabeled.

    func scrollViewDidBeginScrolling(_ scrollView: UIScrollView)
    

    If the method returns Bool (such as those that make an assertion about the delegate’s source object itself), then the method’s name is the delegate’s source type followed by an indicative or conditional verb phrase describing the assertion. The argument is unlabeled.

    func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool
    

    If the method returns some other value (such as those querying for information about a property of the delegate’s source object), then the method’s base name is a noun phrase describing the property being queried. The argument is labeled with a preposition or phrase with a trailing preposition that appropriately combines the noun phrase and the delegate’s source object.

    func numberOfSections(in scrollView: UIScrollView) -> Int
    

    For methods that take additional arguments after the delegate’s source object, the method’s base name is the delegate’s source type by itself and the first argument is unlabeled. Then:

    If the method returns Void, the second argument is labeled with an indicative verb phrase describing the event that has the argument as its direct object or prepositional object, and any other arguments (if present) provide further context.

    func tableView(
      _ tableView: UITableView,
      willDisplayCell cell: UITableViewCell,
      forRowAt indexPath: IndexPath)
    

    If the method returns Bool, the second argument is labeled with an indicative or conditional verb phrase that describes the return value in terms of the argument, and any other arguments (if present) provide further context.

    func tableView(
      _ tableView: UITableView,
      shouldSpringLoadRowAt indexPath: IndexPath,
      with context: UISpringLoadedInteractionContext
    ) -> Bool
    

    If the method returns some other value, the second argument is labeled with a noun phrase and trailing preposition that describes the return value in terms of the argument, and any other arguments (if present) provide further context.

    func tableView(
      _ tableView: UITableView,
      heightForRowAt indexPath: IndexPath
    ) -> CGFloat
    

    Apple’s documentation on delegates and data sources also contains some good general guidance about such names.

⬆ back to top

Style

  • DON'T include types where they can be easily inferred.

    // WRONG
    let host: Host = Host()
    
    // RIGHT
    let host = Host()
    
    enum Direction {
      case left
      case right
    }
    
    func someDirection() -> Direction {
      // WRONG
      return Direction.left
    
      // RIGHT
      return .left
    }
    
    extension Container {
        static let `default` = Container()
    }
    
    // WRONG
    func getContainer() -> Container {
        return Container.default
    }
    
    // RIGHT
    func getContainer() -> Container { .default }
    
    
  • PREFER type inference over explicit typing.

    While similar to the rule on not including types when they can easily be inferred this is slightly different. When declaring properties prefer type inference over explicit typing.
    // WRONG
    let host: Host = Host()
    let arr: [String] = []
    let dict: [AnyHashable: Any] = [:]
    
    // RIGHT
    let host = Host()
    let arr = [String]()
    let dict = [AnyHashable: Any]()
    
  • DON'T use self unless it's necessary for disambiguation or required by the language.

    final class Listing {
    
      init(capacity: Int, allowsPets: Bool) {
        // WRONG
        self.capacity = capacity
        self.isFamilyFriendly = !allowsPets // `self.` not required here
    
        // RIGHT
        self.capacity = capacity
        isFamilyFriendly = !allowsPets
      }
    
      private let isFamilyFriendly: Bool
      private var capacity: Int
    
      private func increaseCapacity(by amount: Int) {
        // WRONG
        self.capacity += amount
    
        // RIGHT
        capacity += amount
    
        // WRONG
        self.save()
    
        // RIGHT
        save()
      }
    }
    
  • DO bind to self when upgrading from a weak reference.

    //WRONG
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request { [weak self] response in
          guard let strongSelf = self else { return }
          // Do work
          completion()
        }
      }
    }
    
    // RIGHT
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request { [weak self] response in
          guard let self = self else { return }
          // Do work
          completion()
        }
      }
    }
    
  • PREFER self in argument capture lists.

     //WRONG
    class MyClass {
      func example {
        performSomeTask {
          self.thing = new
          self.otherThing = otherNew
          self.runSomeMethod()
        }
      }
    }
    
    // RIGHT
    class MyClass {
      func example {
        performSomeTask { [self] in
          thing = new
          otherThing = otherNew
          runSomeMethod()
        }
      }
    }
    
  • DO place the colon immediately after an identifier, followed by a space.

    // WRONG
    var something : Double = 0
    
    // RIGHT
    var something: Double = 0
    
    // WRONG
    class MyClass : SuperClass {
      // ...
    }
    
    // RIGHT
    class MyClass: SuperClass {
      // ...
    }
    
    // WRONG
    var dict = [KeyType:ValueType]()
    var dict = [KeyType : ValueType]()
    
    // RIGHT
    var dict = [KeyType: ValueType]()
    
  • DO place a space on either side of a return arrow for readability.

    // WRONG
    func doSomething()->String {
      // ...
    }
    
    // RIGHT
    func doSomething() -> String {
      // ...
    }
    
    // WRONG
    func doSomething(completion: ()->Void) {
      // ...
    }
    
    // RIGHT
    func doSomething(completion: () -> Void) {
      // ...
    }
    
  • DO omit unnecessary parentheses.

    // WRONG
    if (userCount > 0) { ... }
    switch (someValue) { ... }
    let evens = userCounts.filter { (number) in number % 2 == 0 }
    let squares = userCounts.map() { \$0 * \$0 }
    
    // RIGHT
    if userCount > 0 { ... }
    switch someValue { ... }
    let evens = userCounts.filter { number in number % 2 == 0 }
    let squares = userCounts.map { \$0 * \$0 }
    
  • DO omit get keyword on get-only computed properties.

    // WRONG
    var isEmpty: Bool {
      get {
        return count == 0
      }
    }
    
    // RIGHT
    var isEmpty: Bool { count == 0 }
    
  • DO have brackets on separate lines for multi-line. Put the opening and closing brackets on separate lines from any of the elements of the array. Also add a trailing comma on the last element.

    // WRONG
    let rowContent = [listingUrgencyDatesRowContent(),
                      listingUrgencyBookedRowContent(),
                      listingUrgencyBookedShortRowContent()]
    
    // RIGHT
    let rowContent = [
      listingUrgencyDatesRowContent(),
      listingUrgencyBookedRowContent(),
      listingUrgencyBookedShortRowContent(),
    ]
    
  • PREFER constructors instead of Make() functions for NSRange and others.

    // WRONG
    let range = NSMakeRange(10, 5)
    
    // RIGHT
    let range = NSRange(location: 10, length: 5)
    
  • AVOID using backticks to escape reserved keywords.

    Why?

    Reserved keywords are well...reserved and usually very generic. If you find yourself using overly generic terms your code is likely less readable. A notable exception to this is when dot syntax clears up that ambiguity, for example: Container.default, or even .default in context.

    // WRONG
    @IBOutlet var `switch`: UISwitch!
    
    // RIGHT
    @IBOutlet var notificationSwitch: UISwitch!
    
    extension Container {
        static let `default` = Container()
    }
    
  • DO omit the return keyword when not required by the language.

    // WRONG
    ["1", "2", "3"].compactMap { return Int(\$0) }
    
    var size: CGSize {
      return CGSize(
        width: 100.0,
        height: 100.0)
    }
    
    func makeInfoAlert(message: String) -> UIAlertController {
      return UIAlertController(
        title: "ℹ️ Info",
        message: message,
        preferredStyle: .alert)
    }
    
    // RIGHT
    ["1", "2", "3"].compactMap { Int(\$0) }
    
    var size: CGSize {
      CGSize(
        width: 100.0,
        height: 100.0)
    }
    
    func makeInfoAlert(message: String) -> UIAlertController {
      UIAlertController(
        title: "ℹ️ Info",
        message: message,
        preferredStyle: .alert)
    }
    
  • AVOID using semicolons after each statement in your code. It is only required if you wish to combine multiple statements on a single line.

    
    class SomeClass {
      var name:String?
      private func fooy() {
        // WRONG
        let foo = "foo is a common term in programming";
    
        // RIGHT
        let foo = "foo is a common term in programming" 
    
        // RIGHT
        guard let x = name else { print("there is no name"); return }
      }
    }
    
  • AVOID having more than 1 statement per line. An exception is guard statements when you may need a single statement before the return.

    
    class SomeClass {
      var name:String?
      private func fooy() {
        // WRONG
        let foo = "statement"; let bar = "bar"
        guard let x = name else { logFailure(); makeDebugBreadcrumbs(); completion(); return }
    
        // RIGHT
        let foo = "statement"
        let bar = "bar"
        guard let x = name else {
            logFailure()
            makeDebugBreadcrumbs()
            completion()
            return
        }
      }
    }
    
  • DO break apart numeric literals by groups of three, just like you would use commas.

    // WRONG
    let val = 1000
    let val2 = 10000000
    
    // RIGHT
    let val = 1_000
    let val2 = 10_000_000
    
  • DON'T assign variables through a labeled tuple shuffle. let (label: varName, label2: varName2) = tuple

    Assigning variables through a tuple pattern (sometimes referred to as a tuple shuffle) is only permitted if the left-hand side of the assignment is unlabeled.

    // WRONG
    let (x: a, y: b) = (y: 4, x: 5.0)
    
    // RIGHT
    let (b, a) = (y: 4, x: 5.0)
    
  • PREFER escape sequences over unicode.

    For any character that has a special escape sequence (\t, \n, \r, \", \', \\, and \0), that sequence is used rather than the equivalent Unicode (e.g., \u{000a}) escape sequence.

  • DON'T use invisible characters in code. for example zero-width space characters are illegal

  • PREFER multi-line strings to strings with multiple \n characters.

  • DO put else statements at the end of the last line of a guard clause.

    // WRONG
    guard let a = optional,
          let b = otherOptional
          else {
            return
          }
    
    guard !isEmpty
    else {
      return
    }
    
    // RIGHT
    guard let a = optional,
          let b = otherOptional else {
            return
          }
    
    guard !isEmpty else { return }
    
  • DO use the K&R brace style for braces.

    // WRONG
    func foo()
    {
      // statements
    }
    
    // RIGHT
    func foo() {
      // statements
    }
    
  • PREFER attributes on the same line as what they are attributing for properties, functions, and closures.

    Why?

    Because we naturally read lines of code as a consistent line, introducing whitespace makes it that much harder to understand the full context of the line you're trying to understand. This is the same reasoning used for preferring a return on the same line as the final parameter in a function declaration. A notable exception to this preference is the @available attribute which should always be on its own line.

    NOTE: This does not come at the cost of sanity. If you have an exceptionally large number of attributions then it might indicate that you've gotten overzealous, and it actually obscures your meaning, rather than add to it.

    // WRONG
    @objc
    func thingExposedToObjectiveC() {
      // ...
    }
    
    // WRONG
    @ViewBuilder @discardableResult @MainActor @inlinable func betYouDoNotEvenKnowWhatThisFunctionDoesAnymore() {
      // ...
    }
    
    // WRONG
    @available(iOS 14.0, *) func doSomething() {
      // ...
    }
    
    // RIGHT
    @State var stateVar: Bool = true
    @discardableResult func thing() -> Bool { true }
    @MainActor @ViewBuilder func createViews() -> some View {
      // ...
    }
    
    // RIGHT
    @available(iOS 14.0, *) 
    func doSomething() {
      // ...
    }
    
  • PREFER attributes on a separate line from what they are attributing for classes, structs, and actors.

    Why?

    This is the convention set by Apple in the Swift attribute docs. A notable exception to this preference is the @available attribute which should always be on its own line.

    // WRONG
    @dynamicCallable class MyClass {
      // ...
    }
    
    // WRONG
    @MainActor struct MyStruct {
      // ...
    }
    
    // WRONG
    @available(iOS 14.0, *) @MainActor 
    struct MyStruct {
      // ...
    }
    
    // RIGHT
    @dynamicCallable
    class MyClass {
      // ...
    }
    
    // RIGHT
    @available(iOS 14.0, *) 
    @propertyWrapper 
    struct MyPropertyWrapper {
      // ...
    }
    
    // RIGHT
    @available(iOS 13, *)
    @resultBuilder
    @requires_stored_property_inits
    class WorkflowBuilder {
      // ...
    }
    

Functions

  • PREFER omitting Void return types from function definitions.

    // WRONG
    func doSomething() -> Void {
      ...
    }
    
    // RIGHT
    func doSomething() {
      ...
    }
    
  • DO separate long function declarations with line breaks before each argument label after the first, but not before the return signature. Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter.

    class Universe {
    
      // WRONG
      func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String {
        // This is too long and will probably auto-wrap in a weird way
      }
    
      // WRONG
      func generateStars(
        at location: Point,
        count: Int,
        color: StarColor,
        withAverageDistance averageDistance: Float) -> String {
        populateUniverse() // this line blends in with the argument list
      }
    
      // WRONG
      func generateStars(
        at location: Point,
        count: Int,
        color: StarColor,
        withAverageDistance averageDistance: Float) throws
        -> String {
        populateUniverse() // this line blends in with the argument list
      }
    
      // RIGHT
      func generateStars(at location: Point,
                         count: Int,
                         color: StarColor,
                         withAverageDistance averageDistance: Float) -> String {
          populateUniverse()
      }
    
      // RIGHT
      func generateStars(at location: Point,
                         count: Int,
                         color: StarColor,
                         withAverageDistance averageDistance: Float) throws -> String {
          populateUniverse()
      }
    }
    
  • DO break each argument on long function invocations. Put the closing parenthesis on the last line of the invocation.

    // WRONG
    universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4)
    
    // WRONG
    universe.generateStars(at: location,
                           count: 5,
                           color: starColor,
                           withAverageDistance: 4
                           )
    
    // RIGHT
    universe.generateStars(at: location,
                           count: 5,
                           color: starColor,
                           withAverageDistance: 4)
    
  • AVOID having 1-line functions unless they actually increase readability and trend towards english fluency.

    Why?

    Overly terse code is often difficult to reason about or modify.

    // WRONG
    func didTapBookButton() { User.add(book: books[someIndex]) }
    
    // RIGHT
    var isEmpty: Bool { count == 0 }
    

Closures

  • PREFER Void return types over () in closure declarations. If you must specify a Void return type in a function declaration, use Void rather than () to improve readability.

    // WRONG
    func method(completion: () -> ()) {
      ...
    }
    
    
    // RIGHT
    func method(completion: () -> Void) {
      ...
    }
    
  • DO name unused closure parameters as underscores (_) unless none are used.

    Why?

    Naming unused closure parameters as underscores reduces the cognitive overhead required to read closures by making it obvious which parameters are used and which are unused.

    // WRONG
    someAsyncThing() { argument1, argument2, argument3 in
      print(argument3)
    }
    
    // RIGHT
    someAsyncThing() { _, _, argument3 in
      print(argument3)
    }
    
    // RIGHT
    someAsyncThing() {
      print(\$2)
    }
    
  • PREFER anonymous closure values when there are less than 2 arguments and it does not greatly increase cognitive complexity

    Why?

    Anonymous closure values ($0, $1, $2, etc...) can make it hard to reason able what you're dealing with barring some very specific circumstances. If you have more than 2 anonymous closure arguments you should name them to decrease ambiguity.

    // WRONG
    someAsyncThing() {
        print(\$0)
        modify(\$1)
        if (\$2 == someValue.flatMap ({ \$0 })) { //wait which \$0????? 
        }
    }
    
    // RIGHT
    someAsyncThing() { name, age, isWearingSunglasses in
        print(name)
        modify(age)
        if (isWearingSunglasses == someValue.flatMap ({ \$0 }).isEmpty) {
        }
    }
    
    // RIGHT
    [:].merging([:]) { \$1 }
    
  • DO put a space around single-line closures.

    // WRONG
    let evenSquares = numbers.filter {\$0 % 2 == 0}.map {  \$0 * \$0  }
    
    // RIGHT
    let evenSquares = numbers.filter { \$0 % 2 == 0 }.map { \$0 * \$0 }
    
  • DO use trailing closure syntax. If there are multiple trailing closure parameters, use the new syntax for multiple trailing closures available in Swift 5.x

    
    class SomeClass {
      var name:String?
      private func fooy() {
        // WRONG
        UIView.animate(withDuration: 0.6, animations: { _ in self.view.alpha = 0}) { _ in
            self.view.removeFromSuperview()
        }
    
        // WRONG
        UIView.animate(withDuration: 0.6, animations: { _ in self.view.alpha = 0}, completion: { _ in self.view.removeFromSuperview() })
    
    
        // RIGHT
        UIView.animate(withDuration: 0.3) {
          self.view.alpha = 0
        } completion: { _ in
          self.view.removeFromSuperview()
        }
      }
    }
    
  • AVOID multiple optional trailing closures.

Operators

  • DO put a single space around infix operators. Prefer parenthesis to visually group statements with many operators rather than varying widths of whitespace. This rule does not apply to range operators (e.g. 1...3) and postfix or prefix operators (e.g. guest? or -1).

    // WRONG
    let capacity = 1+2
    let capacity = currentCapacity   ?? 0
    let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected)
    let capacity=newCapacity
    let latitude = region.center.latitude - region.span.latitudeDelta/2.0
    
    // RIGHT
    let capacity = 1 + 2
    let capacity = currentCapacity ?? 0
    let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected)
    let capacity = newCapacity
    let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0)
    
  • AVOID creating custom operators.

    Why?

    Custom operators can drastically decrease readability. While there are times when they can be beneficial, they should either follow other common language conventions (like how ~= is used for regex matching in many languages) or they should have a clear precedent inside the codebase (like a %% postfix operator that has an x percent change of executing, if 10%%)

  • CONSIDER overloading existing operators when your use of the operator is semantically equivalent to the existing uses in the standard library.

    Overloading operators is permitted when your use of the operator is semantically equivalent to the existing uses in the standard library. Examples of permitted use cases are implementing the operator requirements for Equatable and Hashable, or defining a new Matrix type that supports arithmetic operations.

Enumerations

  • DO omit enum associated values from case statements when all arguments are unlabeled.

    // WRONG
    if case .done(_) = result { ... }
    
    switch animal {
    case .dog(_, _, _):
      ...
    }
    
    // RIGHT
    if case .done = result { ... }
    
    switch animal {
    case .dog:
      ...
    }
    
  • DO use Swift's automatic enum values unless they map to an external source. Unless the external source has a value type like String that will not cause issues when inserted in the middle. Add a comment explaining why explicit values are defined.

    Why?

    To minimize user error, improve readability, and write code faster, rely on Swift's automatic enum values. If the value maps to an external source (e.g. it's coming from a network request) or is persisted across binaries, define the values explicitly and document what these values are mapping to. The exception to this is when the value type is like String that will not cause issues when inserted in the middle.

    This ensures that if someone adds a new value in the middle, they won't accidentally break things.

    // WRONG
    enum ErrorType: String {
      case error = "error"
      case warning = "warning"
    }
    
    // These values are internal, so we should not have explicity defined the values.
    enum Planet: Int {
      case mercury = 0
      case venus = 1
      case earth = 2
      case mars = 3
      case jupiter = 4
      case saturn = 5
      case uranus = 6
      case neptune = 7
    }
    
    // These values come from a server, so we should have set them here explicitly to match those values.
    enum ErrorCode: Int {
      case notEnoughMemory
      case invalidResource
      case timeOut
    }
    
    // These values also come from a server, but they are of string type so we should have continued to use the automatic values.
    enum UserType: String {
      case owner = "owner"
      case manager = "manager"
      case member = "member"
    }
    
    // RIGHT
    enum ErrorType: String {
      case error
      case warning
    }
    
    // These values are internal, so we do not need to explicity define the values.
    enum Planet: Int {
      case mercury
      case venus
      case earth
      case mars
      case jupiter
      case saturn
      case uranus
      case neptune
    }
    
    // These values come from a server, so we set them here explicitly to match those values.
    enum ErrorCode: Int {
      case notEnoughMemory = 0
      case invalidResource = 1
      case timeOut = 2
    }
    
    // These values also come from a server, but they are of string type so we can continue to use the automatic values.
    enum UserType: String {
      case owner
      case manager
      case member
    }
    
  • DO put each case on its own line in an enum. The expectation to this is if none of the cases have associated values or raw values, all cases fit on a single line, and the cases do not need further documentation because their meanings are obvious from their names.

      // WRONG
      public enum Token {
        case comma, semicolon, identifier(String)
      }
    
      // RIGHT
      public enum Token {
        case comma
        case semicolon
        case identifier
      }
    
  • DO declare the enum as indirect when all cases must be indirect. Omit the keyword on individual cases.

      // WRONG
      public enum DependencyGraphNode {
        indirect case userDefined(dependencies: [DependencyGraphNode])
        indirect case synthesized(dependencies: [DependencyGraphNode])
      }
    
      // RIGHT
      public indirect enum DependencyGraphNode {
        case userDefined(dependencies: [DependencyGraphNode])
        case synthesized(dependencies: [DependencyGraphNode])
      }
    
  • DO order enum cases in a logical order. If there is no obvious logical ordering, use a lexicographical odering based on the cases' names.

      // WRONG
      // These are sorted lexicographically, but the meaningful groupings of related values has been lost.
      public enum HTTPStatus: Int {
        case badRequest = 400
        case forbidden = 403
        case internalServerError = 500
        case notAuthorized = 401
        case notFound = 404
        case ok = 200
        case paymentRequired = 402
      }
    
      // RIGHT
      public enum HTTPStatus: Int {
        case ok = 200
    
        case badRequest = 400
        case notAuthorized = 401
        case paymentRequired = 402
        case forbidden = 403
        case notFound = 404
    
        case internalServerError = 500
      }
    

Generics

  • DO use generic where clauses for constraints, and use the generic specialization for typing.

    Why?

    We strive for code that delivers relevant information quickly. When reading the below examples from left to right, there is less cognitive load to provide the type of the specialization when the generic is introduced than to retain the generic, its uses, and then retroactively apply the type. It also creates a separation between specialization and restrictions when reading.

    // WRONG
    class SpecializedGeneric<F, B, FB> where F: Foo, B: Bar, FB: FooBar, F.Input == FB.Input {
      init<T, S, O>(thing: T, stuff: S, other: O) where T: Thing, S: Stuff, O: Other, T.Output == String, O.Input == T.Output {}
    }
    
    // RIGHT
    class SpecializedGeneric<F: Foo, B: Bar, FB: FooBar> where F.Input == FB.Input {
      init<T: Thing, S: Stuff, O: Other>(thing: T, stuff: S, other: O) where T.Output == String, O.Input == T.Output {}
    }
    
  • DO use meaningful names when specializing with generics.

    Why?

    Often times with generics we lazily write T and move on, in the right contexts a single letter generic is just fine however as you start getting into increasingly complex scenarios this drastically reduces readability.

    // WRONG
    class Bookshelf<T> {
    
    }
    
    func<T, U, V> doThing() {
    
    }
    
    // RIGHT
    class Bookshelf<ReadableContent> {
    
    }
    
    func<S: Sequence, P: SomeType & SomeProtocol, N: Numeric> doThing() {
    
    }
    

⬆ back to top

Patterns

Initializers

  • PREFER initializing properties at init time whenever possible, rather than using implicitly unwrapped optionals. A notable exception is UIViewController's view property.

    // WRONG
    class MyClass {
      var someValue: Int!
      init() {
        super.init()
        someValue = 5
      }
    }
    
    // RIGHT
    class MyClass {
      var someValue: Int
      init() {
        someValue = 0
        super.init()
      }
    }
    
  • PREFER default values in property declarations over initializers setting values.

    // WRONG
    class MyClass {
      var someValue: Int
      init() {
        someValue = 5
      }
    }
    
    // RIGHT
    class MyClass {
      var someValue: Int = 5
      init() { }
    }
    
  • AVOID performing any meaningful or time-intensive work in init(). Avoid doing things like opening database connections, making network requests, reading large amounts of data from disk, etc. Create a factory if these things need to be done before an object is ready for use.

  • AVOID .init unless dealing with metatypes.

    // WRONG
    let x = MyType.init(arguments)
    let x: MyType = .init(arguments)
    
    // RIGHT
    let x = MyType(arguments)
    
    let type = lookupType(context)
    let x = type.init(arguments)
    
    let x = makeValue(factory: MyType.init)
    
    
  • DON'T call ExpressibleBy*Literal compiler protocols directly.

    // WRONG
    struct Kilometers: ExpressibleByIntegerLiteral {
      init(integerLiteral value: Int) {
        // ...
      }
    }
    
    let k = Kilometers(integerLiteral: 10)
    
    // RIGHT
    struct Kilometers: ExpressibleByIntegerLiteral {
      init(integerLiteral value: Int) {
        // ...
      }
    }
    
    let k1: Kilometers = 10 // GOOD.
    let k2 = 10 as Kilometers // ALSO GOOD.
    

Method Complexity

  • DO extract complex property observers into methods. This reduces nestedness, separates side-effects from property declarations, and makes the usage of implicitly-passed parameters like oldValue explicit.

    // WRONG
    class TextField {
      var text: String? {
        didSet {
          guard oldValue != text else {
            return
          }
    
          // Do a bunch of text-related side-effects.
        }
      }
    }
    
    // RIGHT
    class TextField {
      var text: String? {
        didSet { textDidUpdate(from: oldValue) }
      }
    
      private func textDidUpdate(from oldValue: String?) {
        guard oldValue != text else {
          return
        }
    
        // Do a bunch of text-related side-effects.
      }
    }
    
  • CONSIDER Combine functional chains over completion handlers.

  • DO extract complex callback blocks into methods. This limits the complexity introduced by weak-self in blocks and reduces nestedness. If you need to reference self in the method call, make use of guard to unwrap self for the duration of the callback.

    // WRONG
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request() { [weak self] response in
          if let self = self {
            // Processing and side effects
          }
          completion()
        }
      }
    }
    
    // RIGHT
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request() { [weak self] response in
          guard let self = self else { return }
          self.doSomething(with: self.property, response: response)
          completion()
        }
      }
    
      func doSomething(with nonOptionalParameter: SomeClass, response: SomeResponseClass) {
        // Processing and side effects
      }
    }
    

Control Flow

  • PREFER using guard over if for preconditions.

    // WRONG
    class MyClass {
      var thingWeNeed: String?
      func doThings() {
        if let thing = thingWeNeed {
          // lets do something with the thing
        }
      }
    }
    
    // RIGHT
    class MyClass {
      var thingWeNeed: String?
      func doThings() {
          guard let thing = thingWeNeed else { return }
          // lets do something with the thing
      }
    }
    
    // WRONG
    class MyClass {
      var arr = [String]()
      func doThings() {
        if !arr.isEmpty {
          // lets do something with the thing
        }
      }
    }
    
    // RIGHT
    class MyClass {
      var arr = [String]()
      func doThings() {
          guard !arr.isEmpty else { return }
          // lets do something with the thing
      }
    }
    
    
  • PREFER using guard at the beginning of a scope.

    Why?

    It's easier to reason about a block of code when all guard statements are grouped together at the top rather than intermixed with business logic.

  • DO use the "Golden Path" rule. GOLDEN PATH RULE: When coding with conditionals, the left-hand margin of the code should be the "golden" or "happy" path. That is, don't nest if statements. Multiple return statements are OK. The guard statement is built for this.

    // WRONG
    func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
      if let context = context {
        if let inputData = inputData {
          // use context and input to compute the frequencies
          // notice the "return" line is far to the right, this violates the 'left margin' idea.
          return frequencies
        } else {
          throw FFTError.noInputData
        }
      } else {
        throw FFTError.noContext
      }
    }
    
    // RIGHT
    func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
      guard let context = context else {
        throw FFTError.noContext
      }
      guard let inputData = inputData else {
        throw FFTError.noInputData
      }
    
      // use context and input to compute the frequencies
      // notice the return statement is as far left as it can be, this satisfies the golden path rule.
      return frequencies
    }
    
    
  • DO write if/else statements starting with the happy path.

    Why?

    Your code should read as a declaration of intent. Starting with the happy path case makes your intent more immediately apparent.

    NOTE: This does not conflict with the golden path rule for guards and early exits.

    // WRONG
    if case .failure(let err) = result {
      // handle error
    } else {
      // thing the code should really do
    }
    
    switch result {
      case .failure(let err): throw err
      case .success: //thing the code should do
    }
    
    if thingThatProbablyWillNotHappen || thingThatLikelyWillHappen {
    
    }
    
    // RIGHT
    guard case .success = result else {
      throw err
    }
    
    // thing the code should do
    
    switch result {
      case .success: // thing the code should do
      case .failure(let err): throw err
    }
    
    if thingThatLikelyWillHappen || thingThatProbablyWillNotHappen {
      
    }
    
    
  • DON'T use the default case when switching over an enum.

    Why?

    Enumerating every case requires developers and reviewers have to consider the correctness of every switch statement when new cases are added.

    // WRONG
    switch anEnum {
    case .a:
      // Do something
    default:
      // Do something else.
    }
    
    // RIGHT
    switch anEnum {
    case .a:
      // Do something
    case .b, .c:
      // Do something else.
    }
    
  • DON'T use nested ternaries.

    Why?

    Ternaries can be great, they actually serve a functional purpose over an if statement because they are expressions, so you can assign them to a constant or return them as an expression. That being said if you find yourself nesting them you have gone too far.

    // WRONG
    result = a > b ? x = c > d ? c : d : y
    
    // RIGHT
    result = value != 0 ? x : y
    
  • DON'T use multi-line ternaries.

    A ternary is meant to be used for a very short conditional. If you find it cannot be reasonably expressed on one line then it should not be a ternary.

  • PREFER for where loops when the entirety of a for loop’s body would be a single if block testing a condition of the element.

    // WRONG
    for item in collection {
      if item.hasProperty {
        // ...
      }
    }
    
    // RIGHT
    for item in collection where item.hasProperty {
      // ...
    }
    
  • DO put a var or let before each element in a pattern that is being matched.

    The let and var keywords are placed individually in front of each element in a pattern that is being matched. The shorthand version of let/var that precedes and distributes across the entire pattern is forbidden because it can introduce unexpected behavior if a value being matched in a pattern is itself a variable.

    enum DataPoint {
      case unlabeled(Int)
      case labeled(String, Int)
    }
    
    let label = "goodbye"
    // WRONG
    // `label` is treated as a value here because it is not preceded by `let`, so
    // the pattern below matches only data points that have the label "goodbye".
    switch DataPoint.labeled("hello", 100) {
    case .labeled(label, let value):
      // ...
    }
    
    // RIGHT
    // Writing `let` before each individual binding clarifies that the intent is to
    // introduce a new binding (shadowing the local variable within the case) rather
    // than to match against the value of the local variable. Thus, this pattern
    // matches data points with any string label.
    switch DataPoint.labeled("hello", 100) {
    case .labeled(let label, let value):
      // ...
    }
    

    In the example below, if the author’s intention was to match using the value of the label variable above, that has been lost because let distributes across the entire pattern and thus shadows the variable with a binding that applies to any string value:

    // WRONG
    switch DataPoint.labeled("hello", 100) {
    case let .labeled(label, value):
      // ...
    }
    

    Labels of tuple arguments and enum associated values are omitted when binding a value to a variable with the same name as the label.

    // RIGHT
    enum BinaryTree<Element> {
      indirect case subtree(left: BinaryTree<Element>, right: BinaryTree<Element>)
      case leaf(element: Element)
    }
    
    switch treeNode {
    case .subtree(let left, let right):
      // ...
    case .leaf(let element):
      // ...
    }
    

    Including the labels adds noise that is redundant and lacking useful information:

    // WRONG
    switch treeNode {
    case .subtree(left: let left, right: let right):
      // ...
    case .leaf(element: let element):
      // ...
    }
    

Access Control

  • PREFER the strictest possible access control. Prefer public to open and private to fileprivate unless you need that behavior.

  • AVOID global functions whenever possible. Prefer methods within type definitions.

    // WRONG
    func age(of person, bornAt timeInterval) -> Int {
      // ...
    }
    
    func jump(person: Person) {
      // ...
    }
    
    // RIGHT
    class Person {
      var bornAt: TimeInterval
    
      var age: Int {
        // ...
      }
    
      func jump() {
        // ...
      }
    }
    
  • DO use caseless enums for organizing public or internal constants and functions into namespaces.

    • Avoid creating non-namespaced global constants and functions.
    • Feel free to nest namespaces where it adds clarity.
    • private globals are permitted, since they are scoped to a single file and do not pollute the global namespace. Consider placing private globals in an enum namespace to match the guidelines for other declaration types.

    Why?

    Caseless enums work well as namespaces because they cannot be instantiated, which matches their intent.

    enum Environment {
    
      enum Earth {
        static let gravity = 9.8
      }
    
      enum Moon {
        static let gravity = 1.6
      }
    }
    
  • DO default type methods to static.

    Why?

    If a method needs to be overridden, the author should opt into that functionality by using the class keyword instead.

    // WRONG
    class Fruit {
      class func eatFruits(_ fruits: [Fruit]) { ... }
    }
    
    // RIGHT
    class Fruit {
      static func eatFruits(_ fruits: [Fruit]) { ... }
    }
    
  • DO default classes to final.

    Why?

    If a class needs to be overridden, the author should opt into that functionality by omitting the final keyword.

    // WRONG
    class SettingsRepository {
      // ...
    }
    
    // RIGHT
    final class SettingsRepository {
      // ...
    }
    
  • DO specify the access control for each declaration in an extension individually.

    Why?

    Specifying the access control on the declaration itself helps engineers more quickly determine the access control level of an individual declaration.

    // WRONG
    public extension Universe {
      // This declaration doesn't have an explicit access control level.
      // In all other scopes, this would be an internal function,
      // but because this is in a public extension, it's actually a public function.
      func generateGalaxy() { }
    }
    
    // WRONG
    private extension Spaceship {
      func enableHyperdrive() { }
    }
    
    // RIGHT
    extension Universe {
      // It is immediately obvious that this is a public function,
      // even if the start of the `extension Universe` scope is off-screen.
      public func generateGalaxy() { }
    }
    
    // RIGHT
    extension Spaceship {
      // Recall that a private extension actually has fileprivate semantics,
      // so a declaration in a private extension is fileprivate by default.
      fileprivate func enableHyperdrive() { }
    }
    

Optionals

  • PREFER throwing or optional intializers over optional properties that should have a value.

    // WRONG
    struct Person {
      var firstName: String? // firstName should have a value, but won't necessarily if we just create a new person
      var lastName: String? // lastName may, or may not have a value based on culture
    }
    
    // RIGHT
    struct Person: Decodable {
      var firstName: String // firstName should have a value, if a REST API does not return it Decodable has a throwing intializer that will well...throw
      var lastName: String? // lastName may, or may not have a value based on culture, so it should remain optional
    }
    
    // STILL RIGHT
    struct Person: Decodable {
      var firstName: String // firstName should have a value
      var lastName: String? // lastName may, or may not have a value based on culture, so it should remain optional
    
      init?(_ dictionary: [String: Any]) {
          guard let firstName = dictionary["firstName"] as? String else { return nil }
          self.firstName = firstName
          lastName = dictionary["lastName"] as? String
      }
    }
    
    
  • PREFER implicitly unwrapped optionals when a value can be safely assumed.

    Why?

    Implicitly unwrapped optionals aren't bad, contrary to some opinions. While Swift does give us a lot of safety implicitly unwrapped optionals merely mean "this likely has a value when you need it". You can still treat them like optionals, unwrap them, use optional chaining syntax. Or you can treat them as if they have an expected value. Use them where appropriate and write adequate tests.

    // WRONG
    class ViewController {
      @IBOutlet var textField: UITextField?
    }
    
    // RIGHT
    class ViewController {
      @IBOutlet var textField: UITextField!
    }
    
    // STILL RIGHT
    class ViewModel {
      // where we have a test proving that SomeAPI is registered in the container in the app lifecycle
      @DependencyInjected var someAPI: SomeAPI!
    }
    
    
  • DO check for nil rather than using optional binding if you don't need to use the value.

    Why?

    Checking for nil makes it immediately clear what the intent of the statement is. Optional binding is less explicit.

    var thing: Thing?
    
    // WRONG
    if let _ = thing {
      doThing()
    }
    
    // RIGHT
    if thing != nil {
      doThing()
    }
    
  • AVOID force unwrapping optionals.

    Why?

    Remember, implicitly unwrapped optionals are a thing if you can safely assume a value. A core component to Swift is its safety, don't ruin that safety just for conveniences sake.

    // WRONG
    let index = [].index(of: "hello")!
    // This is NOT thread safe
    tableView?.dataSource?.tableView?(tableView!, commit: .delete, forRowAt: expectedIndexPath)
    
    // RIGHT
    if let index = [].index(of: "hello") {
      // use index
    }
    
    if let tableView = tableView {
      // thread safe
      tableView.dataSource?.tableView?(tableView, commit: .delete, forRowAt: expectedIndexPath)
    }
    
    

Immutability

  • PREFER immutable values whenever possible. Use map and compactMap instead of appending to a new collection. Use filter instead of removing elements from a mutable collection.

    Why?

    Mutable variables increase complexity, so try to keep them in as narrow a scope as possible.

    // WRONG
    var results = [SomeType]()
    for element in input {
      let result = transform(element)
      results.append(result)
    }
    
    // RIGHT
    let results = input.map { transform(\$0) }
    
    // WRONG
    var results = [SomeType]()
    for element in input {
      if let result = transformThatReturnsAnOptional(element) {
        results.append(result)
      }
    }
    
    // RIGHT
    let results = input.compactMap { transformThatReturnsAnOptional(\$0) }
    
  • PREFER structs over classes.

    Why?

    This follows the previous rule of preferring immutability. Structs explicitly mark mutating members as mutating, they favor composition over inheritance, they have synthesized initializers, and they are copy-on-write meaning that unintended side-effects from modifying a reference are less prevalent.

    // WRONG
    class User: Codable {
        var username: String
        var email: String
        var dateOfBirth: Date
    
        init(username: String, email: String, dateOfBirth: Date) {
            self.username = username
            self.email = email
            self.dateOfBirth = dateOfBirth
        }
    }
    
    // RIGHT
    struct User: Codable {
        var username: String
        var email: String
        var dateOfBirth: Date
    }
    

Protocols

  • DO use AnyObject instead of class in protocol definitions.

    Why?

    SE-0156, which introduced support for using the AnyObject keyword as a protocol constraint, recommends preferring AnyObject over class:

    This proposal merges the concepts of class and AnyObject, which now have the same meaning: they represent an existential for classes. To get rid of the duplication, we suggest only keeping AnyObject around. To reduce source-breakage to a minimum, class could be redefined as typealias class = AnyObject and give a deprecation warning on class for the first version of Swift this proposal is implemented in. Later, class could be removed in a subsequent version of Swift.

    // WRONG
    protocol Foo: class {}
    
    // RIGHT
    protocol Foo: AnyObject {}
    
  • PREFER one conformance per extension or type declaration.

    Why?

    Choosing to have multiple conformances in a type means it is more difficult to extract code later, it also makes your type harder to reason about.

    // WRONG
    class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
      // all methods
    }
    
    // RIGHT
    class MyViewController: UIViewController {
      // class stuff here
    }
    
    extension MyViewController: UITableViewDataSource {
      // table view data source methods
    }
    
    extension MyViewController: UIScrollViewDelegate {
      // scroll view delegate methods
    }
    

Type Erasure

  • PREFER a wrapped Any type over a subclass for type erasure.

    Why?

    Subclassing based type erasure inherantly exposes implemention details to the end user that should not be exposed. Wrapping our types in the same way that Combine and SwiftUI do type erasure, ensures that the final exposed API is clean and correct for the end user to consume.

    // WRONG
    public protocol AnyStuff {
        // cannot make internal
        func erasedFoo(_ x: Any)
    }
    
    // End user should not see erasedFoo but can
    public protocol Stuff: AnyStuff {
        associatedtype T
        func foo(_ x: T)
    }
    
    extension Stuff {
        func foo(_ x: T) { erasedFoo(x) }
    }
    
    // RIGHT
    class AnyStuffBase {
        func foo(_ x: Any) { fatalError() }
    }
    
    class AnyStuffStorage<S: Stuff>: AnyStuffBase {
        let holder: S
        init(_ Stuff: S) { holder = Stuff }
    
        override func foo(_ x: Any) { holder.foo(x as! S.T) }
    }
    
    public class AnyStuff {
        private let base: AnyStuffBase
        public init<S: Stuff>(_ stuff: S) { base = AnyStuffStorage(stuff) }
        public func foo(_ x: Any) { base.foo(x) }
    }
    
    public protocol Stuff {
        associatedtype T
        func foo(_ x: T)
    }
    

⬆ back to top

File Organization

  • DO limit empty vertical whitespace to one line. Favor the following formatting guidelines over whitespace of varying heights to divide files into logical groupings.

  • DO end files with a newline.

  • DO place content in the correct order within a file based on impact to the codebase, grouped by similarity. This allows a new reader of your code to more easily find what they are looking for.

    Why?

    Things that can potentially affect more of the codebase go first. Similarly, related things (like the same property wrappers) take sorting precedence over the below list. e.g. @EnvironmentObject comes before @State; public comes before private; @MainActor comes before @ViewBuilder; typealias's are potentially more affecting and thus go at the start.

    Still extract as necessary into extensions, but ensure those extensions also conform to the rule.

    The order:

    • Typealiases
    • Class properties
    • Static properties
    • Instance properties
    • Class methods
    • Static methods
    • Initializers
    • Instance methods
    • Nested types placed into extensions
    public struct ContentView: View {
      // typealias goes first as it impacts the rest of this struct and all other objects.
      // Also these are candidates for extraction into an extension
      public typealias CustomTypeAlias1 = String
      typealias CustomTypeAlias2 = String
      private typealias CustomTypeAlias3 = String
    
      // EnvironmentObject impacts this view and can impact all views beyond this one.
      @EnvironmentObject private var appModel: AppModel
    
      // ObservedObject impacts more than State and thus goes earlier even though hikeResult is internal.
      @ObservedObject private var viewModel = ViewModel()
      @State var hikesResult: Result<[Hike], API.HikesService.Error>?
    
      // inspection impacts everything in body so it goes before body
      let inspection = Inspection<Self>()
    
      var body: some View { Empty() }
    
      // Candidates for extraction into an extension
      public class func classMethods1() {}
      class func classMethods2() {}
      private class func classMethods3() {}
    
      // Candidates for extraction into an extension
      public static func staticMethods1() {}
      static func staticMethods2() {}
      private static func staticMethods3() {}
    
      public init()
      init()
      init?()
      private init()
    
      public func instanceMethod1() {}
      func instanceMethod2() {}
      private func instanceMethod3() {}
    }
    
  • DO name files after the primary type in the file.

  • DO use utf8 string encoding for all your files.

  • DO name extension files in the style *Extensions.

  • PREFER one non-nested type per file.

  • DO use extensions to break apart code into logical groups/files.

  • PREFER grouping by logical types. If there are extensive related models, extensions, classes, other files, then group them together in a group named after their logical meaning.

  • DO sort files by type using the builtin sorting in Xcode File Explorer.

  • DO group imports according to the correct order with a line break between groups. Alphabetizing after grouping is not required.

    The correct order:

    1. Apple imports
    2. 3rd party imports
    3. 1st party imports
    4. @testable and/or import under test
    import Foundation
    import Combine
    
    import Cuckoo
    import Swinject
    import ViewInspector
    
    import SwiftCurrentCore
    
    @testable import SwiftCurrentSwiftUI
    
  • DO put computed properties and properties with property observers at the end of the set of declarations of the same kind. (e.g. instance properties.)

    // WRONG
    var atmosphere: Atmosphere {
      didSet {
        print("oh my god, the atmosphere changed")
      }
    }
    var gravity: CGFloat
    
    // RIGHT
    var gravity: CGFloat
    var atmosphere: Atmosphere {
      didSet {
        print("oh my god, the atmosphere changed")
      }
    }
    
  • DON'T keep dead code around.

    On the surface this may seem obvious but dead code takes many forms. File templates can really hurt you here because when you say create a new UIViewController it has methods that do nothing but call super that counts as dead code and clutters up the codebase needlessly.

    // WRONG
    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
      // Dispose of any resources that can be recreated.
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
      // #warning Incomplete implementation, return the number of sections
      return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      // #warning Incomplete implementation, return the number of rows
      return Database.contacts.count
    }
    
    // RIGHT
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return Database.contacts.count
    }
    
  • AVOID excessive comments.

    We are big believers in self documenting code. Public API deserve documentation comments in all their glory and you should follow our guide on that. When dealing with internal code comments should be reserved for times when meaning is genuinely unclear or non-intuitive. This tends to only be true when you cannot extract to a private method and increase readability.

    // WRONG
    // calculates sum of all the ages of all the users
    func allUserAges() {
      users.reduce(0) { \$0.ageInYears + \$1.ageInYears }
    }
    
    // RIGHT
    /// max: Returns the maximum value in the comparable LinkedList
    /// - Returns: The maximum concrete value in the LinkedList or nil if there is none
    public func max() -> Value? {
        guard var m = first?.value else { return nil }
        forEach { m = Swift.max(m, \$0.value) }
        return m
    }
    
    // STILL RIGHT
    // Implementation of Luhn's Algorithm in Swift
    // From the rightmost digit of your card number, double every other digit.
    // If the doubled digit is larger than 9 (ex. 8 * 2 = 16), subtract 9 from the product (16 – 9 = 7).
    // Sum the digits.
    // If there is no remainder after dividing by 10 (sum % 10 == 0), the card is valid.
    var isValidCreditCardNumber: Bool {
        let digits = reversed().compactMap { Int(String(\$0)) }
        guard digits.count == count, digits.count > 0 else { return false }
        let sum = digits.enumerated().reduce(0) {
            return \$0 + (((\$1.offset % 2) == 0) ? \$1.element : (2 * \$1.element - 1) % 9 + 1)
        }
        return sum % 10 == 0
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      // #warning Incomplete implementation, return the number of rows
      return Database.contacts.count
    }
    
    // RIGHT
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return Database.contacts.count
    }
    

⬆ back to top

Objective-C Interoperability

  • PREFER pure Swift classes over subclasses of NSObject. If your code needs to be used by some Objective-C code, wrap it to expose the desired functionality. Use @objc on individual methods and variables as necessary rather than exposing all API on a class to Objective-C via @objcMembers.

    class PriceBreakdownViewController {
    
      private let acceptButton = UIButton()
    
      private func setUpAcceptButton() {
        acceptButton.addTarget(
          self,
          action: #selector(didTapAcceptButton),
          forControlEvents: .touchUpInside)
      }
    
      @objc private func didTapAcceptButton() {
        // ...
      }
    }
    

⬆ back to top

Attribution: