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.
- This guide is in addition to the official Swift API Design Guidelines. These rules should not contradict that document.
- These rules should not fight Xcode's ^ + I indentation behavior.
- We strive to make every rule lintable:
- If a rule changes the format of the code, it needs to be able to be reformatted automatically (either using SwiftLint autocorrect or SwiftFormat).
- For rules that don't directly change the format of the code, we should have a lint rule that throws a warning.
- Exceptions to these rules should be rare and heavily justified.
- Xcode Formatting
- Naming
- Style
- Patterns
- File Organization
- Objective-C Interoperability
- Dependencies
- Contributors
- Amendments
You can enable the following settings in Xcode by running this script, e.g. as part of a "Run Script" build phase.
-
(link) Each line should have a maximum column width of 140 characters.
-
(link) Use 4 spaces to indent lines.
-
(link) Trim trailing whitespace in all lines.
-
(link) Use PascalCase for type and protocol names, and lowerCamelCase for everything else.
Details
protocol SpaceThing { // ... } class SpaceFleet: SpaceThing { enum Formation { // ... } class Spaceship { // ... } var ships: [Spaceship] = [] static let worldName: String = "Earth" func addShip(_ ship: Spaceship) { // ... } } let myFleet = SpaceFleet()
Exception: You may prefix a private property with an underscore if it is backing an identically-named property or method with a higher access level
Details
There are specific scenarios where a backing a property or method could be easier to read than using a more descriptive name.
- Type erasure
public final class AnyRequester<ModelType>: Requester { public init<T: Requester>(_ requester: T) where T.ModelType == ModelType { _executeRequest = requester.executeRequest } @discardableResult public func executeRequest( _ request: URLRequest, onSuccess: @escaping (ModelType, Bool) -> Void, onFailure: @escaping (Error) -> Void) -> URLSessionCancellable { return _executeRequest(request, session, parser, onSuccess, onFailure) } private let _executeRequest: ( URLRequest, @escaping (ModelType, Bool) -> Void, @escaping (NSError) -> Void) -> URLSessionCancellable }
- Backing a less specific type with a more specific type
final class ExperiencesViewController: UIViewController { // We can't name this view since UIViewController has a view: UIView property. private lazy var _view = CustomView() loadView() { self.view = _view } }
-
(link) Name booleans like
isSpaceship,hasSpacesuit, etc. This makes it clear that they are booleans and not other types. -
(link) Acronyms in names (e.g.
URL) should be all-caps except when it’s the start of a name that would otherwise be lowerCamelCase, in which case it should be uniformly lower-cased.Details
// WRONG class UrlValidator { func isValidUrl(_ URL: URL) -> Bool { // ... } func isProfileUrl(_ URL: URL, for userId: String) -> Bool { // ... } } let URLValidator = UrlValidator() let isProfile = URLValidator.isProfileUrl(URLToTest, userId: IDOfUser) // RIGHT class URLValidator { func isValidURL(_ url: URL) -> Bool { // ... } func isProfileURL(_ url: URL, for userID: String) -> Bool { // ... } } let urlValidator = URLValidator() let isProfile = urlValidator.isProfileUrl(urlToTest, userID: idOfUser)
-
(link) Names should be written with their most general part first and their most specific part last. The meaning of "most general" depends on context, but should roughly mean "that which most helps you narrow down your search for the item you're looking for." Most importantly, be consistent with how you order the parts of your name.
Details
// WRONG let rightTitleMargin: CGFloat let leftTitleMargin: CGFloat let bodyRightMargin: CGFloat let bodyLeftMargin: CGFloat // RIGHT let titleMarginRight: CGFloat let titleMarginLeft: CGFloat let bodyMarginRight: CGFloat let bodyMarginLeft: CGFloat
-
(link) Include a hint about type in a name if it would otherwise be ambiguous.
Details
// WRONG let title: String let cancel: UIButton // RIGHT let titleText: String let cancelButton: UIButton
-
(link) Event-handling functions should be named like past-tense sentences. The subject can be omitted if it's not needed for clarity.
Details
// WRONG class ExperiencesViewController { private func handleBookButtonTap() { // ... } private func modelChanged() { // ... } } // RIGHT class ExperiencesViewController { private func didTapBookButton() { // ... } private func modelDidChange() { // ... } }
-
(link) Avoid Objective-C-style acronym prefixes. This is no longer needed to avoid naming conflicts in Swift.
Details
// WRONG class AIRAccount { // ... } // RIGHT class Account { // ... }
-
(link) Avoid
*Controllerin names of classes that aren't view controllers.
-
(link) Don't include types where they can be easily inferred.
Details
// 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 }
-
(link) Don't use
selfunless it's necessary for disambiguation or required by the language.Details
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() } }
-
(link) Bind to
selfwhen upgrading from a weak reference.Details
//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() } } }
-
(link) Name members of tuples for extra clarity. Rule of thumb: if you've got more than 3 fields, you should probably be using a struct.
Details
// WRONG func whatever() -> (Int, Int) { return (4, 4) } let thing = whatever() print(thing.0) // RIGHT func whatever() -> (x: Int, y: Int) { return (x: 4, y: 4) } // THIS IS ALSO OKAY func whatever2() -> (x: Int, y: Int) { let x = 4 let y = 4 return (x, y) } let coord = whatever() coord.x coord.y
-
(link) Place the colon immediately after an identifier, followed by a space.
Details
// 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]()
-
(link) Place a space on either side of a return arrow for readability.
Details
// WRONG func doSomething()->String { // ... } // RIGHT func doSomething() -> String { // ... }
// WRONG func doSomething(completion: ()->Void) { // ... } // RIGHT func doSomething(completion: () -> Void) { // ... }
-
(link) Places spaces around Braces, Brackets and removes Spaces that arent required NOTE: Not all the rules are linked here, check SwiftFormat
Details
// WRONG func doSomething()->String { // ... } // RIGHT func doSomething() -> String { // ... }
// WRONG func doSomething(completion: ( Int )->Void) { // ... } // RIGHT func doSomething(completion: (Int) -> Void) { // ... }
// WRONG let x = values.map{$0} // RIGHT let x = values.map { $0 }
// WRONG values.map( { /**/ } ) // RIGHT values.map({ /**/ })
-
(link) Omit unnecessary parentheses.
Details
// 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 }
-
(link) Omit enum associated values from case statements when all arguments are unlabeled.
Details
// WRONG if case .done(_) = result { ... } switch animal { case .dog(_, _, _): ... } // RIGHT if case .done = result { ... } switch animal { case .dog: ... }
-
(link) Place function/type attributes on the line above the declaration.
Details
// WRONG @objc class Spaceship { @discardableResult func fly() -> Bool { } } // RIGHT @objc class Spaceship { @discardableResult func fly() -> Bool { } }
-
(link) Multi-line arrays should have each bracket on a separate 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.
Details
// WRONG let rowContent = [listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent()] let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent() ] // RIGHT let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent(), ]
-
(link) Multi-line conditional statements should break after the leading keyword. Indent each individual statement by 4 spaces.
Details
Breaking after the leading keyword resets indentation to the standard 2-space grid, which helps avoid fighting Xcode's ^ + I indentation behavior.
// WRONG if let galaxy = galaxy, galaxy.name == "Milky Way" // Indenting by two spaces fights Xcode's ^+I indentation behavior { … } // WRONG guard let earth = unvierse.find( .planet, named: "Earth"), earth.isHabitable // Blends in with previous condition's method arguments else { … } // Right guard let galaxy = galaxy, galaxy.name == "Milky Way" else { … } // RIGHT if let galaxy = galaxy, galaxy.name == "Milky Way" { … }
-
(link) Use constructors instead of Make() functions for NSRange and others.
Details
// WRONG let range = NSMakeRange(10, 5) // RIGHT let range = NSRange(location: 10, length: 5)
-
(link) Omit
Voidreturn types from function definitions.Details
// WRONG func doSomething() -> Void { ... } // RIGHT func doSomething() { ... }
-
(link) Separate long function declarations with line breaks before each argument label and 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.
Details
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 { // Xcode indents all the arguments } // 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() } }
-
(link) Long function invocations should also break on each argument. Put the closing parenthesis on the last line of the invocation.
Details
// WRONG universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4) // WRONG universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4) // WRONG universe.generateStars( at: location, count: 5, color: starColor, withAverageDistance: 4 ) // WRONG universe.generate(5, .stars, at: location) // RIGHT universe.generateStars( at: location, count: 5, color: starColor, withAverageDistance: 4) // RIGHT universe.generate( 5, .stars, at: location)
-
(link) Favor
Voidreturn types over()in closure declarations. If you must specify aVoidreturn type in a function declaration, useVoidrather than()to improve readability.Details
// WRONG func method(completion: () -> ()) { ... } // RIGHT func method(completion: () -> Void) { ... }
-
(link) Name unused closure parameters as underscores (
_).Details
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) }
-
(link) Single-line closures should have a space inside each brace.
Details
// WRONG let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 } // RIGHT let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
-
(link) Infix operators should have a single space on either side. 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).Details
// 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)
-
(link) 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 something like astart()method if these things need to be done before an object is ready for use. -
(link) Extract complex property observers into methods. This reduces nestedness, separates side-effects from property declarations, and makes the usage of implicitly-passed parameters like
oldValueexplicit.Details
// 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. } }
-
(link) 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
guardto unwrap self for the duration of the callback.Details
//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 } }
-
(link) Prefer using
guardat the beginning of a scope. -
(link) Access control should be at the strictest level possible. Prefer
publictoopenandprivatetofileprivateunless you need that behavior. -
(link) Avoid global functions whenever possible. Prefer methods within type definitions.
Details
// WRONG func age(of person, bornAt timeInterval) -> Int { // ... } func jump(person: Person) { // ... } // RIGHT class Person { var bornAt: TimeInterval var age: Int { // ... } func jump() { // ... } }
-
(link) Use caseless
enums for organizingpublicorinternalconstants and functions into namespaces.- Avoid creating non-namespaced global constants and functions.
- Feel free to nest namespaces where it adds clarity.
privateglobals are permitted, since they are scoped to a single file and do not pollute the global namespace. Consider placing private globals in anenumnamespace to match the guidelines for other declaration types.
-
(link) Use Swift's automatic enum values unless they map to an external source. Add a comment explaining why explicit values are defined.
Details
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, however, define the values explicity, and document what these values are mapping to.
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" } enum UserType: String { case owner case manager case member } 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 } enum ErrorCode: Int { case notEnoughMemory case invalidResource case timeOut } // RIGHT enum ErrorType: String { case error case warning } /// These are written to a logging service. Explicit values ensure they're consistent across binaries. // swiftlint:disable redundant_string_enum_value enum UserType: String { case owner = "owner" case manager = "manager" case member = "member" } // swiftlint:enable redundant_string_enum_value enum Planet: Int { case mercury case venus case earth case mars case jupiter case saturn case uranus case neptune } /// These values come from the server, so we set them here explicitly to match those values. enum ErrorCode: Int { case notEnoughMemory = 0 case invalidResource = 1 case timeOut = 2 }
-
(link) Use optionals only when they have semantic meaning.
-
(link) Prefer immutable values whenever possible. Use
mapandcompactMapinstead of appending to a new collection. Usefilterinstead of removing elements from a mutable collection.Details
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) }
-
(link) Default type methods to
static. -
(link) Default classes to
final. -
(link) Never use the
defaultcase whenswitching over an enum.Details
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. }
-
(link) Check for nil rather than using optional binding if you don't need to use the value.
-
(link) Omit the
returnkeyword when not required by the language.Details
// 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) }
-
(link) Use
AnyObjectinstead ofclassin protocol definitions.Details
SE-0156, which introduced support for using the
AnyObjectkeyword as a protocol constraint, recommends preferringAnyObjectoverclass:This proposal merges the concepts of
classandAnyObject, which now have the same meaning: they represent an existential for classes. To get rid of the duplication, we suggest only keepingAnyObjectaround. To reduce source-breakage to a minimum,classcould be redefined astypealias class = AnyObjectand give a deprecation warning on class for the first version of Swift this proposal is implemented in. Later,classcould be removed in a subsequent version of Swift.// WRONG protocol Foo: class {} // RIGHT protocol Foo: AnyObject {}
-
(link) Specify the access control for each declaration in an extension individually.
Details
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() { } }
-
(link) Avoid casting protocols to concrete types.
Details
Downcasting a
protocolto a concrete type (egclass) can introduce potential errors when the type cannot be casted. If you require methods on the concrete type not present in theprotocol, amend theprotocolto add them, or refactor the code to use the concrete type instead. There will be times where downcasting is unavoidable (due to third party libraries, or Obj-c code), but we would want to avoid it if we can.// WRONG protocol UserProvider { // ... } final class UserManager: UserProvider { // } if let manager = provider as? UserManager { // downcasted manager } // RIGHT - One of the following alternatives // Add the needed method to the protocol protocol UserProvider { func doThing() } final class UserManager: UserProvider { } let provider: UserProvider provider.doThing() // OR - Use the concrete type instead. protocol UserProvider { } final class UserManager: UserProvider { func doThing() } let manager: UserManager manager.doThing() // OR - Add an additional protocol protocol UserProvider { } protocol UserManagerProtocol: UserProvider { func doThing() } final class UserManager: UserManagerProtocol { } let manager: UserManagerProtocol manager.doThing()
-
(link) Prefer failing loudly over silently.
Details
Failing loudly allows us to catch errors in development and testing. Our automated tools (Crashlytics etc.) are well suited to detecting loud errors like crashes, but are poor at detecting silent errors like missing or blank UI, freezes, early returns, etc.
For further granularity, prefer
assertfor catching errors which might be easily uncovered in debug mode, and are recoverable. Useprecondition,preconditionFailureorfatalErrorfor when something should absolutely never occur or the error is unrecoverable.// WRONG guard let viewModel = model else { return } view.loadWith(viewModel) // RIGHT guard let viewModel = model else { fatalError("The viewModel must be present to render this view!") } view.loadWith(viewModel)
-
(link) Prefer failing at compile time over at runtime.
Details
Failing at compilation time allows us to catch errors in development. Failing at runtime may introduce unexpected crashes and bugs. Make code as "compile-safe" as possible so that potential failure cases are uncovered during compilation.
// WRONG struct Thing: Decodable { let type: String } switch thing.type { case "post": // case "catch": // default: // this should never occur } // RIGHT struct Thing: Decodable { let type: ThingType } enum ThingType { case catch case post } switch thing.type { case .post: // case .catch: // }
-
(link) Alphabetize and deduplicate module imports within a file. Place all imports at the top of the file below the header comments. Do not add additional line breaks between import statements. Add a single empty line before the first import and after the last import.
Details
- A standard organization method helps engineers more quickly determine which modules a file depends on.
- Duplicated import statements have no effect and should be removed for clarity.
// WRONG // Copyright © 2018 Airbnb. All rights reserved. // import DLSPrimitives import Constellation import Constellation import Epoxy import Foundation //RIGHT // Copyright © 2018 Airbnb. All rights reserved. // import Constellation import DLSPrimitives import Epoxy import Foundation
Exception:
@testable importshould be grouped after the regular import and separated by an empty line.Details
// WRONG // Copyright © 2018 Airbnb. All rights reserved. // import DLSPrimitives @testable import Epoxy import Foundation import Nimble import Quick //RIGHT // Copyright © 2018 Airbnb. All rights reserved. // import DLSPrimitives import Foundation import Nimble import Quick @testable import Epoxy
-
(link) Import only the modules a source file requires. For example, don't import
UIKitwhen importingFoundationwill suffice. Likewise, don't importFoundationif you must importUIKit.Details
// WRONG import UIKit import Foundation var view: UIView var deviceModels: [String] // RIGHT import UIKit var view: UIView var deviceModels: [String] // RIGHT import Foundation var deviceModels: [String]
-
(link) Limit empty vertical whitespace to one line. Favor the following formatting guidelines over whitespace of varying heights to divide files into logical groupings.
-
(link) Files should end in a newline.
-
(link) Use
// MARK:to separate the contents of type definitions and extensions into the sections listed below, in order. All type definitions and extensions should be divided up in this consistent way, allowing a reader of your code to easily jump to what they are interested in.// MARK: Lifecycleforinitanddeinitmethods.// MARK: Openforopenproperties and methods.// MARK: Publicforpublicproperties and methods.// MARK: Internalforinternalproperties and methods.// MARK: Fileprivateforfileprivateproperties and methods.// MARK: Privateforprivateproperties and methods.- If the type in question is an enum, its cases should go above the first
// MARK:. - Do not subdivide each of these sections into subsections, as it makes the method dropdown more cluttered and therefore less useful. Instead, group methods by functionality and use smart naming to make clear which methods are related. If there are enough methods that sub-sections seem necessary, consider refactoring your code into multiple types.
- If all of the type or extension's definitions belong to the same category (e.g. the type or extension only consists of
internalproperties), it is OK to omit the// MARK:s. - If the type in question is a simple value type (e.g. fewer than 20 lines), it is OK to omit the
// MARK:s, as it would hurt legibility.
-
(link) Within each top-level section, place content in the following order. This allows a new reader of your code to more easily find what they are looking for.
- Nested types and typealiases
- Static properties
- Class properties
- Instance properties
- Static methods
- Class methods
- Instance methods
-
(link) Add empty lines between property declarations of different kinds. (e.g. between static properties and instance properties.)
Details
// WRONG static let gravityEarth: CGFloat = 9.8 static let gravityMoon: CGFloat = 1.6 var gravity: CGFloat // RIGHT static let gravityEarth: CGFloat = 9.8 static let gravityMoon: CGFloat = 1.6 var gravity: CGFloat
-
(link) Computed properties and properties with property observers should appear at the end of the set of declarations of the same kind. (e.g. instance properties.)
Details
// 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") } }
-
(link) Protocol conformance should be added in a separate extension. Prefer adding a separate extension for the protocol methods. This keeps the related methods grouped together with the protocol and can simplify instructions to add a protocol to a class with its associated methods.
Details
// WRONG class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate { // all methods } // RIGHT class MyViewController: UIViewController { // class stuff here } // MARK: - UITableViewDataSource extension MyViewController: UITableViewDataSource { // table view data source methods } // MARK: - UIScrollViewDelegate extension MyViewController: UIScrollViewDelegate { // scroll view delegate methods }
-
(link) 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
@objcon individual methods and variables as necessary rather than exposing all API on a class to Objective-C via@objcMembers.Details
class PriceBreakdownViewController { private let acceptButton = UIButton() private func setUpAcceptButton() { acceptButton.addTarget( self, action: #selector(didTapAcceptButton), forControlEvents: .touchUpInside) } @objc private func didTapAcceptButton() { // ... } }
-
(link) Strongly consider alternatives before adding dependencies.
Details
Dependencies introduce overhead on the development cycle, including increased compilation time, time spent updating dependencies, more difficulty in debugging, and licensing or security issues.
Before adding a new dependency to the project, consider:
- Is this a non-trivial problem that I am solving? Trivial programming problems shouldn't be solved by adding dependencies (eg left-pad )
- Can I just include the code I need? Copying small amounts code or files from open-source libraries can be a solution if you only need part of a larger library. This is assuming the copied code is relatively stable and feature-complete.
- Is the dependency well-maintained? Prefer adding dependencies with active development and community support. If a dependency has lots of unsolved github issues from years ago, or many abandoned PRs, avoid it.
- Are there licensing or security consideration? Pay extra attention if you plan on modifying the library (eg GPL licenses you cannot modify without maintaining the open-source nature of the modified code)
-
(link) Avoid using dependencies directly. Write wrappers around dependencies instead.
Details
Writing wrapper
protocolscan help isolate dependencies so that they may be more easily replaced by internal code or other dependencies. This also reduces the amount ofimport Frameworkwe are doing.protocol RemoteConfigProvider { func remoteConfigValue(forKey: String) } extension FirebaseRemoteConfiguration: RemoteConfigProvider { } // use `RemoteConfigProvider` elsewhere
We encourage you to fork this guide and change the rules to fit your team’s style guide. Below, you may list some amendments to the style guide. This allows you to periodically update your style guide without having to deal with merge conflicts.