Skip to content

Swift Style Guides

OrlaM edited this page Apr 10, 2023 · 14 revisions

This page is a work in progress. Many of the style rules we follow are covered by Swiftlint and we are working on adding more.

Some rules we've worked out we all like:

  • Selectors: #selector(ClassName.methodName) is clearer than #selector(methodName).
  • Type inference: .white is better than UIColor.white.
  • Use of self is contentious.

Async Code Conventions

Completion blocks on-main

Callers expect completion blocks to run on the main thread, this helps to contain off-main thread code such that the caller has no threading considerations/risk. It is rare that the result of a long-running I/O call (such as networking, db access, file access) needs to be handled off main, but if your code does this, please document clearly.

func exampleOfCallingBackOnMainThread(completion: () -> Void) { 
  DispatchQueue.global().async {
    doLotsOfWork()
    DispatchQueue.main.async { 
      completion() // completion is on main, good!
    }
  }
}

Use of Deferred

An alternative to completion() calls in async functions is the use of Deferred. The completion way is standard in iOS and familiar to all developers. However, the syntactical sugar of Deferred makes chaining of async operations much cleaner. It is up to the eng to decide which is best for their use-case. Deferred is particular useful for async-filled variables, and is less contentious that is the right choice in this case.

Error Handling

Be wary of bubbling up errors. In most cases, in-place error handling is much easier to reason about, and to ensure error cases are properly considered. Sometimes the callers are the place that has the requisite knowledge to make decisions about how errors should be handled. But in many cases, functions that bubble errors are risking the callers ignoring those errors. Solution in many cases is to use an optional value, and in-place use Sentry logging and assert as-needed. In the function that is the source of the error it typically looks like:

if somestrangeerror {
 Sentry.shared.send("somestrangerror happened")
 assert(false)
 return nil
}

The assert will crash on debug-only, which is often enough to catch errors, and by-default Sentry will be Beta-only. So these are no-risk to the release builds. In rare cases we might only be hitting an error case in release, which is an acceptable risk.

Clone this wiki locally