This iOS Swift Style Guide is intended to define the styles and guidelines used by Bottle Rocket iOS developers when writing code in Swift. The main goal is to promote a clean, consistent style of writing code that ensures the maintainability, consistency and ease of developer collaboration within our organization. All code written at Bottle Rocket should adhere to the standards defined herein, unless a specific need to violate these guidelines arises.
Where any ambiguity exists in the standards, we default to the Apple standards and guidelines. Apple conventions are strongly encouraged.
For any styling concerns not covered by our SwiftLint configuration, refer to the notes below. If you see something missing, then please open an issue or pull request!
- Classes should follow a consistent structure. Properties should be declared first, followed by initializers, subclass overrides, public methods, and finally private methods.
- Extensions on a custom type should generally be defined in the same source file below the definition of the type itself.
- Extensions on Apple's types should be placed in their own file, typically inside of an "Extensions" folder in your project.
class SomeClass: BaseClass {
private static let privateStaticProperty = // ...
static let staticProperty = // ...
private let privateProperty = // ...
let publicProperty = // ...
init() {
// ...
}
override func someFunc() {
super.someFunc()
// ...
}
func publicMethod() {
// ...
}
private func privateMethod() {
// ...
}
}
extension SomeClass: SomeProtocol {
// ...
}extension SomeClass: SomeProtocol {
// ...
}
class SomeClass: BaseClass {
let publicProperty = // ...
override func someFunc() {
super.someFunc()
// ...
}
init() {
// ...
}
private let privateProperty = // ...
private func privateMethod() {
// ...
}
func publicMethod() {
// ...
}
}- Be restrictive. Always default to the most restrictive access control level possible -
private. Not only is this a good encapsulation practice in general, but it also allows the Swift compiler to automatically perform additional optimizations. - Avoid overspecification. Prefer relying on the implicit
internalaccess control to explicitly declaring something asinternal.
class SomeClass {
private var somePrivateProperty: String?
var someInternalProperty: String?
func someInternalMethod() {
// ...
}
private func somePrivateMethod() {
// ...
}
}public class SomeClass {
var somePrivateProperty: String?
var someInternalProperty: String?
public func someInternalMethod() {
// ...
}
func somePrivateMethod() {
// ...
}
}- Keep high-level entry points clean (
init(),viewDidLoad(),application(_:didFinishLaunchingWithOptions:), etc.). These methods should primarily consist of calls into other methods. This forces you to group related code together and helps to prevent the pollution of high-level methods with implementation details.
class ViewController: UIViewController {
@IBOutlet private var stackView: UIStackView!
@IBOutlet private var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupTextField()
setupSubmitButton()
}
private func setupView() {
view.backgroundColor = .gray
}
private func setupTextField() {
textField.placeholder = "Placeholder"
textField.layer.cornerRadius = 5
textField.delegate = self
}
private func setupSubmitButton() {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.layer.cornerRadius = 5
submitButton.layer.masksToBounds = true
submitButton.isEnabled = false
stackView.addArrangedSubview(submitButton)
}
}class ViewController: UIViewController {
@IBOutlet private var stackView: UIStackView!
@IBOutlet private var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
textField.placeholder = "Placeholder"
textField.layer.cornerRadius = 5
textField.delegate = self
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.layer.cornerRadius = 5
submitButton.layer.masksToBounds = true
submitButton.isEnabled = false
stackView.addArrangedSubview(submitButton)
}
}- Optionals are what inherently allow apps written in Swift to achieve a much lower crash rate than their counterpart written in Objective-C.
- Forced unwrapping of optionals is not permitted. Instead, proper error handling should be employed through the use of a safe unwrapping mechanism like
guard,if let, optional chaining, or nil-coalescing.
var someOptional: String?
guard let text = someOptional else { return }
titleLabel.text = textvar someOptional: String?
if let text = someOptional {
titleLabel.text = text
}var someOptional: String?
titleLabel.text = someOptional ?? "default value"var someOptional: String?
titleLabel.text = someOptional!- For the same reasons outlined above regarding forced unwrapping, forced casting is not permitted.
func parseParameters(from dictionary: [String: Any]) {
guard let firstName = dictionary["first_name"] as? String,
let lastName = dictionary["last_name"] as? String else { return }
// ...
}func parseParameters(from dictionary: [String: Any]) {
let firstName = dictionary["first_name"] as! String
let lastName = dictionary["last_name"] as! String
// ...
}- Implicitly unwrapped optionals should be avoided as well. In general, there are only certain situations where their use is acceptable:
- IBOutlets - Unconnected outlets are development errors which, once correct, will never fail due to dynamic circumstances.
- Unit testing - The system under test (or its dependencies) when it's created/destroyed as part of the
XCTestCaselifecycle (i.e.setUp()andtearDown()).
class ViewController: UIViewController {
var requiredData: [String: Any]?
override func viewDidLoad() {
super.viewDidLoad()
guard let requiredData = requiredData else {
assertionFailure("Required data was not set before presenting \(self)")
return
}
processData(requiredData)
}
private func processData(_ data: [String: Any]) {
// ...
}
}class ViewController: UIViewController {
var requiredData: [String: Any]!
override func viewDidLoad() {
super.viewDidLoad()
processData(requiredData)
}
private func processData(_ data: [String: Any]) {
// ...
}
}- Prefer Swift's native types (
Int,Float,String, etc.) to the older Objective-C types (NSInteger,CGFloat,NSString, etc.). You can always bridge Swift's native types into Objective-C types whenever necessary.
var height: Float = 25.0
let numberOfRows: Int = 5let basePath = "https://bottlerocketstudios.com" // String
let fullPath = (basePath as NSString).appendingPathComponent("contact") // Stringvar height: CGFloat = 25.0
let numberOfRows: NSInteger = 5let basePath = NSString(string: "https://bottlerocketstudios.com") // NSString
let fullPath = basePath.appendingPathComponent("contact") // String- Prefer using extensions to make classes conform to protocols rather than directly implementing them at the class definition.
- Avoid using extensions to create separations between various parts of your code (e.g. private methods, lifecycle methods, etc.). Use
MARK: -comments instead.
class ViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ...
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ...
}
// MARK: - Private
private func somePrivateMethod() {
// ...
}
private func someOtherPrivateMethod() {
// ...
}
}
extension ViewController: UITableViewDataSource {
// ...
}
extension ViewController: UITableViewDelegate {
// ...
}class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// ...
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ...
}
}
extension ViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ...
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ...
}
}
extension ViewController {
private func somePrivateMethod() {
// ...
}
private func someOtherPrivateMethod() {
// ...
}
}- Default to
private. This keeps things better encapsulated, but still allows you to access the outlets in Interface Builder. - Default to a strong reference. There is no need to make IBOutlets
weakin modern iOS code. - Suffix the name with they type of object (e.g.
title**Label**,logo**ImageView**). - Use the most specific type suffix (e.g.
profileImageViewoverprofileView).
@IBOutlet private var logoImageView: UIImageView!@IBOutlet var logo: UIImageView!@IBOutlet var logoView: UIImageView!- Make use of Swift's
lazykeyword to lazy load properties rather than performing the setup logic in hooks likeinit()orviewDidLoad(). - Take extra care to use
lazyfor objects that are expensive to create (e.g.NSDateFormatter).
class ViewController: UIViewController {
private lazy var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
view.addSubview(activityIndicator)
return activityIndicator
}()
override func viewDidLoad() {
super.viewDidLoad()
showSpinner()
loadDataFromNetwork()
}
private func showSpinner() {
activityIndicator.startAnimating()
}
private func loadDataFromNetwork() {
// ...
}
}class SomeTableViewCell: UITableViewCell {
private lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
// Configure formatter
return formatter
}()
func configureWithDate(_ date: Date) {
textLabel?.text = dateFormatter.string(from: date)
// ...
}
}class ViewController: UIViewController {
private var activityIndicator: UIActivityIndicatorView?
override func viewDidLoad() {
super.viewDidLoad()
if activityIndicator == nil {
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
view.addSubview(activityIndicator)
self.activityIndicator = activityIndicator
}
activityIndicator?.startAnimating()
loadDataFromNetwork()
}
private func loadDataFromNetwork() {
// ...
}
}class SomeTableViewCell: UITableViewCell {
func configureWithDate(_ date: Date) {
let dateFormatter = DateFormatter()
// Configure formatter
textLabel?.text = dateFormatter.string(from: date)
// ...
}
}- Keep the scope of constants as small as possible.
- If a constant is only needed for a class, it should live in that class. Another option would be to make it a
static letproperty of afileprivateenum inside of the same source file. - App-wide constants should be kept outside of the global namespace. Instead, make them
static letproperties on an enum. Using a struct would generally be okay as well, but an enum offers an additional protection against accidental instantiation.
enum Constants {
static let globalConstant = // ...
}class ViewController: UIViewController {
private let someConstant = // ...
// ...
}class SomeObject {
enum ObjectConstants {
static let someConstant = // ...
}
}import UIKit
let globalConstant = // ...
let otherGlobalConstant = // ...
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// ...
}import UIKit
let someConstant = // ...
class ViewController: UIViewController {
// ...
}- Enum cases should be declared on separate lines instead of declaring multiple cases on a single line.
enum MyEnum {
case caseOne
case caseTwo
case caseThree
}enum MyEnum {
case caseOne, caseTwo, caseThree
}- There should be exactly one line of whitespace between function declarations.
open func cancel(with error: Error) {
// ...
}
open func cancel(withErrors errors: [Error]) {
// ...
}open func cancel(with error: Error) {
// ...
}
open func cancel(withErrors errors: [Error]) {
// ...
}- There should be exactly one line of whitespace after the opening declaration for an extension.
extension Operation {
open func add(condition: Condition) {
// ...
}
open func add(observer: Observer) {
// ...
}
}extension Operation {
open func add(condition: Condition) {
// ...
}
open func add(observer: Observer) {
// ...
}
}- Prefer immutable variable declarations over mutable declarations whenever possible. This not only allows us to make reasonable assumptions about the use of that variable in code, but also assists the complier.
let foo = ...var foo = ...- Make sure the class you are using this for actually needs to be a singleton. Odds are, it doesn't.
- Prefer the name
sharedfor the singleton instance as opposed tosharedInstance,sharedClassName, etc. - While there are several ways to create a singleton in Swift, the most concise method described below should be preferred on all Bottle Rocket projects.
class MyClass() {
static let shared = MyClass()
init() {
// ...
}
}class MyClass() {
private static let shared: MyClass = {
// ...
}
init() {
// ...
}
static func shared() -> MyClass {
return shared
}
}- Use optionals to enforce validation of input parameters. Prefer using
guardstatements to verify parameters and return early instead of nestingif letstatements.
func cachedImage(url: URL?) -> UIImage? {
guard let absoluteString = url?.absoluteString, let data = imageCache.object(forKey: absoluteString) as? Data else { return nil }
return UIImage(data: data)
}func cachedImage(url: URL?) -> UIImage? {
if let absoluteString = url?.absoluteString {
if let data = imageCache.object(forKey: absoluteString) as? Data {
return turnImageIntoDeliciousSandwich(UIImage(data: data))
}
}
return nil
}- When you're accessing an element in a dictionary or array, prefer the square bracket operators over
objectAtIndex(and similar) methods. These operators make for readable, straightforward code. - In addition, prefer the use of the
first,last, andisEmptyproperties on arrays.
let theElement = array[14]
guard !array.isEmpty else { return }
guard let theFirstOne = array.first else { return }
guard let theLastOne = array.last else { return }let theElement = array.object(at: 14)
guard array.count > 0 else { return }
let theFirstOne = array[0]
let theLastOne = array[array.count - 1]- Avoid hard-coding constants for values that can be calculated for easier readability.
- Document any other hard-coded values with a comment.
let myColor = UIColor(red: 230 / 255, green: 220 / 255, blue: 225 / 255, alpha: 1)let timeUntilRefresh = 60 * 60 * 24 * 2 // 2 dayslet myColor = UIColor(red: 0.901961, green: 0.862745, blue: 0.882353, alpha: 1)let timeUntilRefresh = 172800 // 2 days- Omit type information whenever possible. Let the compiler infer the type of a constant, variable, parameter, etc. This not only applies to enums, but also the majority of static properties (e.g.
UIColor.blackbecomes simply.black).
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: {
// ...
}) { _ in
// ...
}let someBool = truelet music: Genre = .metalUIView.animate(withDuration: 0.1, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {
// ...
}) { _ in
// ...
}let someBool: Bool = truelet music: Genre = Genre.metal- Prefer the syntactic sugar variants of Swift's collection and optional types as opposed to the full generics syntax.
var shoppingList: [String]var itemCount: [String: Int]var phoneNumber: String?var shoppingList: Array<String>var itemCount: Dictionary<String, Int>var phoneNumber: Optional<String>- The
$0shorthand syntax that's available on collection operations is generally fine to use, but you might consider using a named variable if implementing a very complicated operation. Always make sure that other people (including your future self) can easily understand what the$0represents when they read your code.
static func generateRawQueryParametersString(from queryParameters: [URLQueryItem]) -> String {
return queryParameters.reduce("") { (partialResult, queryItem) -> String in
let nextPartialResult = (partialResult.isEmpty ? "" : "\(partialResult)&")
guard let queryValue = queryItem.value else {
return nextPartialResult + "\(queryItem.name)"
}
return nextPartialResult + "\(queryItem.name)=\(queryValue)"
}
}static func generateRawQueryParametersString(from queryParameters: [URLQueryItem]) -> String {
return queryParameters.reduce("") {
return ($0.isEmpty ? "" : "\($0)&").appending($1.value != nil ? "\($1.name)=\($1.value ?? "")" : $1.name)
}
}- Acronyms should always appear in all caps, unless the acronym starts at the beginning of the variable name.
let resultFromAPI = parseResponse()let apiResult = parseResponse()let resultFromApi = parseResponse()let APIResult = parseResponse()- Do not prefix Swift types. Due to Swift offering proper namespacing, this convention from Objective-C is no longer necessary.
- If you do need to expose a class to Objective-C and want to avoid namespace collisions, you can use the
@objc(...)attribute to specify a class name that includes the prefix, but will only be visible to Objective-C code.
class MyAwesomeSwiftClass {
// ...
}@objc(BRSMyAwesomeSwiftClass) class MyAwesomeSwiftClass {
// ...
}class BRSMyAwesomeSwiftClass {
// ...
}- Variable/property names should not be prefixed with an
_, even if used privately. Variables should use headless camel case and not begin with a symbol or number.
struct MyStruct {
private var backingInteger = 0
var mutableInteger: Int {
get {
return backingInteger
} set {
backingInteger = newValue
}
}
}struct MyStruct {
private var _mutableInteger = 0
var mutableInteger: Int {
get {
return _mutableInteger
} set {
_mutableInteger = newValue
}
}
}- Make extensive use of
MARK:comments (#pragma markin Obj-C) to break your class up into sections. This forces you to group related code together, making it easier to browse through files. - Add a dash after the
MARK:so that Xcode's method browser will include nice visual separators:
class ViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet private var someButton: UIButton!
// MARK: - Properties
private var someData: Data?
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
// MARK: - IBActions
@IBAction private func someButtonTapped(_ button: UIButton) {
// ...
}
// MARK: - Private
private func somePrivateMethod() {
// ...
}
}class ViewController: UIViewController {
@IBOutlet private var someButton: UIButton!
private var someData: Data?
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
@IBAction private func someButtonTapped(_ button: UIButton) {
// ...
}
// MARK: Private
private func somePrivateMethod() {
// ...
}
}