├── PULL_REQUEST_TEMPLATE.md
├── resources
├── airbnb.swiftformat
├── xcode_settings.bash
└── swiftlint.yml
├── LICENSE.md
├── CONTRIBUTING.md
└── README.md
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### Summary
2 |
3 |
4 |
5 | #### Reasoning
6 |
7 |
8 |
9 | #### Reviewers
10 | cc @airbnb/swift-styleguide-maintainers
11 |
12 | _Please react with 👍/👎 if you agree or disagree with this proposal._
13 |
--------------------------------------------------------------------------------
/resources/airbnb.swiftformat:
--------------------------------------------------------------------------------
1 | # options
2 | --self remove # redundantSelf
3 | --importgrouping testable-bottom # sortedImports
4 | --commas always # trailingCommas
5 | --trimwhitespace always # trailingSpace
6 |
7 | # rules
8 | --rules redundantParens,redundantSelf,sortedImports,trailingCommas,trailingSpace
9 |
--------------------------------------------------------------------------------
/resources/xcode_settings.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | defaults write com.apple.dt.Xcode DVTTextEditorTrimTrailingWhitespace -bool YES
6 | defaults write com.apple.dt.Xcode DVTTextEditorTrimWhitespaceOnlyLines -bool YES
7 |
8 | defaults write com.apple.dt.Xcode DVTTextIndentTabWidth -int 2
9 | defaults write com.apple.dt.Xcode DVTTextIndentWidth -int 2
10 |
11 | defaults write com.apple.dt.Xcode DVTTextPageGuideLocation -int 100
12 |
--------------------------------------------------------------------------------
/resources/swiftlint.yml:
--------------------------------------------------------------------------------
1 | whitelist_rules:
2 | - closure_spacing
3 | - colon
4 | - empty_enum_arguments
5 | - fatal_error_message
6 | - force_cast
7 | - force_try
8 | - force_unwrapping
9 | - implicitly_unwrapped_optional
10 | - legacy_cggeometry_functions
11 | - legacy_constant
12 | - legacy_constructor
13 | - legacy_nsgeometry_functions
14 | - operator_usage_whitespace
15 | - redundant_string_enum_value
16 | - redundant_void_return
17 | - return_arrow_whitespace
18 | - trailing_newline
19 | - type_name
20 | - unused_closure_parameter
21 | - unused_optional_binding
22 | - vertical_whitespace
23 | - void_return
24 | - custom_rules
25 |
26 | excluded:
27 | - Carthage
28 | - Pods
29 |
30 | colon:
31 | apply_to_dictionaries: false
32 |
33 | indentation: 2
34 |
35 | custom_rules:
36 | no_objcMembers:
37 | name: "@objcMembers"
38 | regex: "@objcMembers"
39 | message: "Explicitly use @objc on each member you want to expose to Objective-C"
40 | severity: error
41 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2012 Airbnb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | To contribute a new style rule first fork the repo and create your branch from `master`. Then open a PR and propose the rule following the structure below.
4 |
5 | If you have an idea that's not completely fleshed out, please [open an issue](https://github.com/airbnb/swift/issues/new) to discuss.
6 |
7 | ## Structure of a new rule:
8 |
9 | At minimum every rule should contain:
10 |
11 | 1. A permalink to reference easily.
12 | 1. A short description.
13 | 1. A link to the appropriate [SwiftLint](https://github.com/realm/SwiftLint) / [SwiftFormat](https://github.com/nicklockwood/SwiftFormat) rule.
14 | 1. _(optional)_ A "Why?" section describing the reasoning behind the rule.
15 | 1. A code example describing the incorrect and correct behaviours.
16 |
17 | #### Example:
18 |
19 | * (link)
20 | **This is the description of the rule.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#some_rule) [](https://github.com/nicklockwood/SwiftFormat#rules)
21 |
22 |
23 |
24 | #### Why?
25 | This is an explanation of why this rule is needed.
26 |
27 | ```swift
28 | // WRONG
29 | func someIncorrectCode {}
30 |
31 | // GOOD
32 | func someGoodCode {}
33 | ```
34 |
35 |
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Medium Swift Style Guide
2 |
3 | ## Goals
4 |
5 | Following this style guide should:
6 |
7 | * Make it easier to read and begin understanding unfamiliar code.
8 | * Make code easier to maintain.
9 | * Reduce simple programmer errors.
10 | * Reduce cognitive load while coding.
11 | * Keep discussions on diffs focused on the code's logic rather than its style.
12 |
13 | 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.
14 |
15 | ## Guiding Tenets
16 |
17 | * This guide is in addition to the official [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/). These rules should not contradict that document.
18 | * These rules should not fight Xcode's ^ + I indentation behavior.
19 | * We strive to make every rule lintable:
20 | * If a rule changes the format of the code, it needs to be able to be reformatted automatically (either using [SwiftLint](https://github.com/realm/SwiftLint) autocorrect or [SwiftFormat](https://github.com/nicklockwood/SwiftFormat)).
21 | * For rules that don't directly change the format of the code, we should have a lint rule that throws a warning.
22 | * Exceptions to these rules should be rare and heavily justified.
23 |
24 | ## Table of Contents
25 |
26 | 1. [Xcode Formatting](#xcode-formatting)
27 | 1. [Naming](#naming)
28 | 1. [Style](#style)
29 | 1. [Functions](#functions)
30 | 1. [Closures](#closures)
31 | 1. [Operators](#operators)
32 | 1. [Patterns](#patterns)
33 | 1. [File Organization](#file-organization)
34 | 1. [Objective-C Interoperability](#objective-c-interoperability)
35 | 1. [Contributors](#contributors)
36 | 1. [Amendments](#amendments)
37 |
38 | ## Xcode Formatting
39 |
40 | _You can enable the following settings in Xcode by running [this script](resources/xcode_settings.bash), e.g. as part of a "Run Script" build phase._
41 |
42 | * (link) **Each line should have a maximum column width of 100 characters.**
43 |
44 |
45 |
46 | #### Why?
47 | Due to larger screen sizes, we have opted to choose a page guide greater than 80
48 |
49 |
50 |
51 | * (link) **Use 2 spaces to indent lines.**
52 |
53 | * (link) **Trim trailing whitespace in all lines.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#trailingSpace)
54 |
55 | **[⬆ back to top](#table-of-contents)**
56 |
57 | ## Naming
58 |
59 | * (link) **Use PascalCase for type and protocol names, and lowerCamelCase for everything else.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#type-name)
60 |
61 |
62 |
63 | ```swift
64 | protocol SpaceThing {
65 | // ...
66 | }
67 |
68 | class SpaceFleet: SpaceThing {
69 |
70 | enum Formation {
71 | // ...
72 | }
73 |
74 | class Spaceship {
75 | // ...
76 | }
77 |
78 | var ships: [Spaceship] = []
79 | static let worldName: String = "Earth"
80 |
81 | func addShip(_ ship: Spaceship) {
82 | // ...
83 | }
84 | }
85 |
86 | let myFleet = SpaceFleet()
87 | ```
88 |
89 |
90 |
91 | _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_
92 |
93 |
94 |
95 | #### Why?
96 | There are specific scenarios where a backing a property or method could be easier to read than using a more descriptive name.
97 |
98 | - Type erasure
99 |
100 | ```swift
101 | public final class AnyRequester: Requester {
102 |
103 | public init(_ requester: T) where T.ModelType == ModelType {
104 | _executeRequest = requester.executeRequest
105 | }
106 |
107 | @discardableResult
108 | public func executeRequest(
109 | _ request: URLRequest,
110 | onSuccess: @escaping (ModelType, Bool) -> Void,
111 | onFailure: @escaping (Error) -> Void) -> URLSessionCancellable
112 | {
113 | return _executeRequest(request, session, parser, onSuccess, onFailure)
114 | }
115 |
116 | private let _executeRequest: (
117 | URLRequest,
118 | @escaping (ModelType, Bool) -> Void,
119 | @escaping (NSError) -> Void) -> URLSessionCancellable
120 |
121 | }
122 | ```
123 |
124 | - Backing a less specific type with a more specific type
125 |
126 | ```swift
127 | final class ExperiencesViewController: UIViewController {
128 | // We can't name this view since UIViewController has a view: UIView property.
129 | private lazy var _view = CustomView()
130 |
131 | loadView() {
132 | self.view = _view
133 | }
134 | }
135 | ```
136 |
137 |
138 |
139 | * (link) **Name booleans like `isSpaceship`, `hasSpacesuit`, etc.** This makes it clear that they are booleans and not other types.
140 |
141 | * (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.**
142 |
143 |
144 |
145 | ```swift
146 | // WRONG
147 | class UrlValidator {
148 |
149 | func isValidUrl(_ URL: URL) -> Bool {
150 | // ...
151 | }
152 |
153 | func isUrlReachable(_ URL: URL) -> Bool {
154 | // ...
155 | }
156 | }
157 |
158 | let URLValidator = UrlValidator().isValidUrl(/* some URL */)
159 |
160 | // RIGHT
161 | class URLValidator {
162 |
163 | func isValidURL(_ url: URL) -> Bool {
164 | // ...
165 | }
166 |
167 | func isURLReachable(_ url: URL) -> Bool {
168 | // ...
169 | }
170 | }
171 |
172 | let urlValidator = URLValidator().isValidURL(/* some URL */)
173 | ```
174 |
175 |
176 |
177 | * (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.
178 |
179 |
180 |
181 | ```swift
182 | // WRONG
183 | let rightTitleMargin: CGFloat
184 | let leftTitleMargin: CGFloat
185 | let bodyRightMargin: CGFloat
186 | let bodyLeftMargin: CGFloat
187 |
188 | // RIGHT
189 | let titleMarginRight: CGFloat
190 | let titleMarginLeft: CGFloat
191 | let bodyMarginRight: CGFloat
192 | let bodyMarginLeft: CGFloat
193 | ```
194 |
195 |
196 |
197 | * (link) **Include a hint about type in a name if it would otherwise be ambiguous.**
198 |
199 |
200 |
201 | ```swift
202 | // WRONG
203 | let title: String
204 | let cancel: UIButton
205 |
206 | // RIGHT
207 | let titleText: String
208 | let cancelButton: UIButton
209 | ```
210 |
211 |
212 |
213 | * (link) **Event-handling functions should be named like past-tense sentences.** The subject can be omitted if it's not needed for clarity.
214 |
215 |
216 |
217 | ```swift
218 | // WRONG
219 | class ExperiencesViewController {
220 |
221 | private func handleBookButtonTap() {
222 | // ...
223 | }
224 |
225 | private func modelChanged() {
226 | // ...
227 | }
228 | }
229 |
230 | // RIGHT
231 | class ExperiencesViewController {
232 |
233 | private func didTapBookButton() {
234 | // ...
235 | }
236 |
237 | private func modelDidChange() {
238 | // ...
239 | }
240 | }
241 | ```
242 |
243 |
244 |
245 | * (link) **Avoid Objective-C-style acronym prefixes.** This is no longer needed to avoid naming conflicts in Swift.
246 |
247 |
248 |
249 | ```swift
250 | // WRONG
251 | class AIRAccount {
252 | // ...
253 | }
254 |
255 | // RIGHT
256 | class Account {
257 | // ...
258 | }
259 | ```
260 |
261 |
262 |
263 | * (link) **Avoid `*Controller` in names of classes that aren't view controllers.**
264 |
265 |
266 | #### Why?
267 | Controller is an overloaded suffix that doesn't provide information about the responsibilities of the class.
268 |
269 |
270 |
271 | **[⬆ back to top](#table-of-contents)**
272 |
273 | ## Style
274 |
275 | * (link) **Don't include types where they can be easily inferred.**
276 |
277 |
278 |
279 | ```swift
280 | // WRONG
281 | let host: Host = Host()
282 |
283 | // RIGHT
284 | let host = Host()
285 | ```
286 |
287 | ```swift
288 | enum Direction {
289 | case left
290 | case right
291 | }
292 |
293 | func someDirection() -> Direction {
294 | // WRONG
295 | return Direction.left
296 |
297 | // RIGHT
298 | return .left
299 | }
300 | ```
301 |
302 |
303 |
304 | * (link) **Don't use `self` unless it's necessary for disambiguation or required by the language.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#redundantSelf)
305 |
306 |
307 |
308 | ```swift
309 | final class Listing {
310 |
311 | init(capacity: Int, allowsPets: Bool) {
312 | // WRONG
313 | self.capacity = capacity
314 | self.isFamilyFriendly = !allowsPets // `self.` not required here
315 |
316 | // RIGHT
317 | self.capacity = capacity
318 | isFamilyFriendly = !allowsPets
319 | }
320 |
321 | private let isFamilyFriendly: Bool
322 | private var capacity: Int
323 |
324 | private func increaseCapacity(by amount: Int) {
325 | // WRONG
326 | self.capacity += amount
327 |
328 | // RIGHT
329 | capacity += amount
330 |
331 | // WRONG
332 | self.save()
333 |
334 | // RIGHT
335 | save()
336 | }
337 | }
338 | ```
339 |
340 |
341 |
342 | * (link) **Add a trailing comma on the last element of a multi-line array.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#trailingCommas)
343 |
344 |
345 |
346 | ```swift
347 | // WRONG
348 | let rowContent = [
349 | listingUrgencyDatesRowContent(),
350 | listingUrgencyBookedRowContent(),
351 | listingUrgencyBookedShortRowContent()
352 | ]
353 |
354 | // RIGHT
355 | let rowContent = [
356 | listingUrgencyDatesRowContent(),
357 | listingUrgencyBookedRowContent(),
358 | listingUrgencyBookedShortRowContent(),
359 | ]
360 | ```
361 |
362 |
363 |
364 | * (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.
365 |
366 |
367 |
368 | ```swift
369 | // WRONG
370 | func whatever() -> (Int, Int) {
371 | return (4, 4)
372 | }
373 | let thing = whatever()
374 | print(thing.0)
375 |
376 | // RIGHT
377 | func whatever() -> (x: Int, y: Int) {
378 | return (x: 4, y: 4)
379 | }
380 |
381 | // THIS IS ALSO OKAY
382 | func whatever2() -> (x: Int, y: Int) {
383 | let x = 4
384 | let y = 4
385 | return (x, y)
386 | }
387 |
388 | let coord = whatever()
389 | coord.x
390 | coord.y
391 | ```
392 |
393 |
394 |
395 | * (link) **Use constructors instead of Make() functions for CGRect, CGPoint, NSRange and others.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-constructor)
396 |
397 |
398 |
399 | ```swift
400 | // WRONG
401 | let rect = CGRectMake(10, 10, 10, 10)
402 |
403 | // RIGHT
404 | let rect = CGRect(x: 0, y: 0, width: 10, height: 10)
405 | ```
406 |
407 |
408 |
409 | * (link) **Favor modern Swift extension methods over older Objective-C global methods.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-cggeometry-functions) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-constant) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-nsgeometry-functions)
410 |
411 |
412 |
413 | ```swift
414 | // WRONG
415 | var rect = CGRectZero
416 | var width = CGRectGetWidth(rect)
417 |
418 | // RIGHT
419 | var rect = CGRect.zero
420 | var width = rect.width
421 | ```
422 |
423 |
424 |
425 | * (link) **Place the colon immediately after an identifier, followed by a space.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#colon)
426 |
427 |
428 |
429 | ```swift
430 | // WRONG
431 | var something : Double = 0
432 |
433 | // RIGHT
434 | var something: Double = 0
435 | ```
436 |
437 | ```swift
438 | // WRONG
439 | class MyClass : SuperClass {
440 | // ...
441 | }
442 |
443 | // RIGHT
444 | class MyClass: SuperClass {
445 | // ...
446 | }
447 | ```
448 |
449 | ```swift
450 | // WRONG
451 | var dict = [KeyType:ValueType]()
452 | var dict = [KeyType : ValueType]()
453 |
454 | // RIGHT
455 | var dict = [KeyType: ValueType]()
456 | ```
457 |
458 |
459 |
460 | * (link) **Place a space on either side of a return arrow for readability.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#returning-whitespace)
461 |
462 |
463 |
464 | ```swift
465 | // WRONG
466 | func doSomething()->String {
467 | // ...
468 | }
469 |
470 | // RIGHT
471 | func doSomething() -> String {
472 | // ...
473 | }
474 | ```
475 |
476 | ```swift
477 | // WRONG
478 | func doSomething(completion: ()->Void) {
479 | // ...
480 | }
481 |
482 | // RIGHT
483 | func doSomething(completion: () -> Void) {
484 | // ...
485 | }
486 | ```
487 |
488 |
489 |
490 | * (link) **Omit unnecessary parentheses.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#redundantParens)
491 |
492 |
493 |
494 | ```swift
495 | // WRONG
496 | if (userCount > 0) { ... }
497 | switch (someValue) { ... }
498 | let evens = userCounts.filter { (number) in number % 2 == 0 }
499 | let squares = userCounts.map() { $0 * $0 }
500 |
501 | // RIGHT
502 | if userCount > 0 { ... }
503 | switch someValue { ... }
504 | let evens = userCounts.filter { number in number % 2 == 0 }
505 | let squares = userCounts.map { $0 * $0 }
506 | ```
507 |
508 |
509 |
510 | * (link) **Omit enum associated values from case statements when all arguments are unlabeled.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#empty-enum-arguments)
511 |
512 |
513 |
514 | ```swift
515 | // WRONG
516 | if case .done(_) = result { ... }
517 |
518 | switch animal {
519 | case .dog(_, _, _):
520 | ...
521 | }
522 |
523 | // RIGHT
524 | if case .done = result { ... }
525 |
526 | switch animal {
527 | case .dog:
528 | ...
529 | }
530 | ```
531 |
532 |
533 |
534 | ### Functions
535 |
536 | * (link) **Omit `Void` return types from function definitions.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#redundant-void-return)
537 |
538 |
539 |
540 | ```swift
541 | // WRONG
542 | func doSomething() -> Void {
543 | ...
544 | }
545 |
546 | // RIGHT
547 | func doSomething() {
548 | ...
549 | }
550 | ```
551 |
552 |
553 |
554 | ### Closures
555 |
556 | * (link) **Favor `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. [](https://github.com/realm/SwiftLint/blob/master/Rules.md#void-return)
557 |
558 |
559 |
560 | ```swift
561 | // WRONG
562 | func method(completion: () -> ()) {
563 | ...
564 | }
565 |
566 | // RIGHT
567 | func method(completion: () -> Void) {
568 | ...
569 | }
570 | ```
571 |
572 |
573 |
574 | * (link) **Name unused closure parameters as underscores (`_`).** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#unused-closure-parameter)
575 |
576 |
577 |
578 | #### Why?
579 | Naming unused closure parameters as underscores reduces the cognitive overhead required to read
580 | closures by making it obvious which parameters are used and which are unused.
581 |
582 | ```swift
583 | // WRONG
584 | someAsyncThing() { argument1, argument2, argument3 in
585 | print(argument3)
586 | }
587 |
588 | // RIGHT
589 | someAsyncThing() { _, _, argument3 in
590 | print(argument3)
591 | }
592 | ```
593 |
594 |
595 |
596 | * (link) **Single-line closures should have a space inside each brace.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#closure-spacing)
597 |
598 |
599 |
600 | ```swift
601 | // WRONG
602 | let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 }
603 |
604 | // RIGHT
605 | let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
606 | ```
607 |
608 |
609 |
610 | ### Operators
611 |
612 | * (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`). [](https://github.com/realm/SwiftLint/blob/master/Rules.md#operator-usage-whitespace)
613 |
614 |
615 |
616 | ```swift
617 | // WRONG
618 | let capacity = 1+2
619 | let capacity = currentCapacity ?? 0
620 | let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected)
621 | let capacity=newCapacity
622 | let latitude = region.center.latitude - region.span.latitudeDelta/2.0
623 |
624 | // RIGHT
625 | let capacity = 1 + 2
626 | let capacity = currentCapacity ?? 0
627 | let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected)
628 | let capacity = newCapacity
629 | let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0)
630 | ```
631 |
632 |
633 |
634 | **[⬆ back to top](#table-of-contents)**
635 |
636 | ## Patterns
637 |
638 | * (link) **Prefer initializing properties at `init` time whenever possible, rather than using implicitly unwrapped optionals.** A notable exception is UIViewController's `view` property. [](https://github.com/realm/SwiftLint/blob/master/Rules.md#implicitly-unwrapped-optional)
639 |
640 |
641 |
642 | ```swift
643 | // WRONG
644 | class MyClass: NSObject {
645 |
646 | init() {
647 | super.init()
648 | someValue = 5
649 | }
650 |
651 | var someValue: Int!
652 | }
653 |
654 | // RIGHT
655 | class MyClass: NSObject {
656 |
657 | init() {
658 | someValue = 0
659 | super.init()
660 | }
661 |
662 | var someValue: Int
663 | }
664 | ```
665 |
666 |
667 |
668 | * (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 a `start()` method if these things need to be done before an object is ready for use.
669 |
670 | * (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 `oldValue` explicit.
671 |
672 |
673 |
674 | ```swift
675 | // WRONG
676 | class TextField {
677 | var text: String? {
678 | didSet {
679 | guard oldValue != text else {
680 | return
681 | }
682 |
683 | // Do a bunch of text-related side-effects.
684 | }
685 | }
686 | }
687 |
688 | // RIGHT
689 | class TextField {
690 | var text: String? {
691 | didSet { textDidUpdate(from: oldValue) }
692 | }
693 |
694 | private func textDidUpdate(from oldValue: String?) {
695 | guard oldValue != text else {
696 | return
697 | }
698 |
699 | // Do a bunch of text-related side-effects.
700 | }
701 | }
702 | ```
703 |
704 |
705 |
706 | * (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 `guard` to unwrap self for the duration of the callback.
707 |
708 |
709 |
710 | ```swift
711 | //WRONG
712 | class MyClass {
713 |
714 | func request(completion: () -> Void) {
715 | API.request() { [weak self] response in
716 | if let strongSelf = self {
717 | // Processing and side effects
718 | }
719 | completion()
720 | }
721 | }
722 | }
723 |
724 | // RIGHT
725 | class MyClass {
726 |
727 | func request(completion: () -> Void) {
728 | API.request() { [weak self] response in
729 | guard let strongSelf = self else { return }
730 | strongSelf.doSomething(strongSelf.property)
731 | completion()
732 | }
733 | }
734 |
735 | func doSomething(nonOptionalParameter: SomeClass) {
736 | // Processing and side effects
737 | }
738 | }
739 | ```
740 |
741 |
742 |
743 | * (link) **Prefer using `guard` at the beginning of a scope.**
744 |
745 |
746 |
747 | #### Why?
748 | 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.
749 |
750 |
751 |
752 | * (link) **Access control should be at the strictest level possible.** Prefer `public` to `open` and `private` to `fileprivate` unless you need that behavior.
753 |
754 | * (link) **Avoid global functions whenever possible.** Prefer methods within type definitions.
755 |
756 |
757 |
758 | ```swift
759 | // WRONG
760 | func age(of person, bornAt timeInterval) -> Int {
761 | // ...
762 | }
763 |
764 | func jump(person: Person) {
765 | // ...
766 | }
767 |
768 | // RIGHT
769 | class Person {
770 | var bornAt: TimeInterval
771 |
772 | var age: Int {
773 | // ...
774 | }
775 |
776 | func jump() {
777 | // ...
778 | }
779 | }
780 | ```
781 |
782 |
783 |
784 | * (link) **Prefer putting constants in the top level of a file if they are `private`.** If they are `public` or `internal`, define them as static properties, for namespacing purposes.
785 |
786 |
787 |
788 | ```swift
789 | private let privateValue = "secret"
790 |
791 | public class MyClass {
792 |
793 | public static let publicValue = "something"
794 |
795 | func doSomething() {
796 | print(privateValue)
797 | print(MyClass.publicValue)
798 | }
799 | }
800 | ```
801 |
802 |
803 |
804 | * (link) **Use caseless `enum`s 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.
805 |
806 |
807 |
808 | #### Why?
809 | Caseless `enum`s work well as namespaces because they cannot be instantiated, which matches their intent.
810 |
811 | ```swift
812 | enum Environment {
813 |
814 | enum Earth {
815 | static let gravity = 9.8
816 | }
817 |
818 | enum Moon {
819 | static let gravity = 1.6
820 | }
821 | }
822 | ```
823 |
824 |
825 |
826 | * (link) **Use Swift's automatic enum values unless they map to an external source.** Add a comment explaining why explicit values are defined. [](https://github.com/realm/SwiftLint/blob/master/Rules.md#redundant-string-enum-value)
827 |
828 |
829 |
830 | #### Why?
831 | 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.
832 |
833 | This ensures that if someone adds a new value in the middle, they won't accidentally break things.
834 |
835 | ```swift
836 | // WRONG
837 | enum ErrorType: String {
838 | case error = "error"
839 | case warning = "warning"
840 | }
841 |
842 | enum UserType: String {
843 | case owner
844 | case manager
845 | case member
846 | }
847 |
848 | enum Planet: Int {
849 | case mercury = 0
850 | case venus = 1
851 | case earth = 2
852 | case mars = 3
853 | case jupiter = 4
854 | case saturn = 5
855 | case uranus = 6
856 | case neptune = 7
857 | }
858 |
859 | enum ErrorCode: Int {
860 | case notEnoughMemory
861 | case invalidResource
862 | case timeOut
863 | }
864 |
865 | // RIGHT
866 | enum ErrorType: String {
867 | case error
868 | case warning
869 | }
870 |
871 | /// These are written to a logging service. Explicit values ensure they're consistent across binaries.
872 | // swiftlint:disable redundant_string_enum_value
873 | enum UserType: String {
874 | case owner = "owner"
875 | case manager = "manager"
876 | case member = "member"
877 | }
878 | // swiftlint:enable redundant_string_enum_value
879 |
880 | enum Planet: Int {
881 | case mercury
882 | case venus
883 | case earth
884 | case mars
885 | case jupiter
886 | case saturn
887 | case uranus
888 | case neptune
889 | }
890 |
891 | /// These values come from the server, so we set them here explicitly to match those values.
892 | enum ErrorCode: Int {
893 | case notEnoughMemory = 0
894 | case invalidResource = 1
895 | case timeOut = 2
896 | }
897 | ```
898 |
899 |
900 |
901 | * (link) **Use optionals only when they have semantic meaning.**
902 |
903 | * (link) **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.
904 |
905 |
906 |
907 | #### Why?
908 | Mutable variables increase complexity, so try to keep them in as narrow a scope as possible.
909 |
910 | ```swift
911 | // WRONG
912 | var results = [SomeType]()
913 | for element in input {
914 | let result = transform(element)
915 | results.append(result)
916 | }
917 |
918 | // RIGHT
919 | let results = input.map { transform($0) }
920 | ```
921 |
922 | ```swift
923 | // WRONG
924 | var results = [SomeType]()
925 | for element in input {
926 | if let result = transformThatReturnsAnOptional(element) {
927 | results.append(result)
928 | }
929 | }
930 |
931 | // RIGHT
932 | let results = input.compactMap { transformThatReturnsAnOptional($0) }
933 | ```
934 |
935 |
936 |
937 | * (link) **Handle an unexpected but recoverable condition with an `assert` method combined with the appropriate logging in production. If the unexpected condition is not recoverable, prefer a `precondition` method or `fatalError()`.** This strikes a balance between crashing and providing insight into unexpected conditions in the wild. Only prefer `fatalError` over a `precondition` method when the failure message is dynamic, since a `precondition` method won't report the message in the crash report. [](https://github.com/realm/SwiftLint/blob/master/Rules.md#fatal-error-message) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#force-cast) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#force-try) [](https://github.com/realm/SwiftLint/blob/master/Rules.md#force-unwrapping)
938 |
939 |
940 |
941 | ```swift
942 | func didSubmitText(_ text: String) {
943 | // It's unclear how this was called with an empty string; our custom text field shouldn't allow this.
944 | // This assert is useful for debugging but it's OK if we simply ignore this scenario in production.
945 | guard !text.isEmpty else {
946 | assertionFailure("Unexpected empty string")
947 | return
948 | }
949 | // ...
950 | }
951 |
952 | func transformedItem(atIndex index: Int, from items: [Item]) -> Item {
953 | precondition(index >= 0 && index < items.count)
954 | // It's impossible to continue executing if the precondition has failed.
955 | // ...
956 | }
957 |
958 | func makeImage(name: String) -> UIImage {
959 | guard let image = UIImage(named: name, in: nil, compatibleWith: nil) else {
960 | fatalError("Image named \(name) couldn't be loaded.")
961 | // We want the error message so we know the name of the missing image.
962 | }
963 | return image
964 | }
965 | ```
966 |
967 |
968 |
969 | * (link) **Default type methods to `static`.**
970 |
971 |
972 |
973 | #### Why?
974 | If a method needs to be overridden, the author should opt into that functionality by using the `class` keyword instead.
975 |
976 | ```swift
977 | // WRONG
978 | class Fruit {
979 | class func eatFruits(_ fruits: [Fruit]) { ... }
980 | }
981 |
982 | // RIGHT
983 | class Fruit {
984 | static func eatFruits(_ fruits: [Fruit]) { ... }
985 | }
986 | ```
987 |
988 |
989 |
990 | * (link) **Default classes to `final`.**
991 |
992 |
993 |
994 | #### Why?
995 | If a class needs to be overridden, the author should opt into that functionality by omitting the `final` keyword.
996 |
997 | ```swift
998 | // WRONG
999 | class SettingsRepository {
1000 | // ...
1001 | }
1002 |
1003 | // RIGHT
1004 | final class SettingsRepository {
1005 | // ...
1006 | }
1007 | ```
1008 |
1009 |
1010 |
1011 | * (link) **Never use the `default` case when `switch`ing over an enum.**
1012 |
1013 |
1014 |
1015 | #### Why?
1016 | Enumerating every case requires developers and reviewers have to consider the correctness of every switch statement when new cases are added.
1017 |
1018 | ```swift
1019 | // WRONG
1020 | switch anEnum {
1021 | case .a:
1022 | // Do something
1023 | default:
1024 | // Do something else.
1025 | }
1026 |
1027 | // RIGHT
1028 | switch anEnum {
1029 | case .a:
1030 | // Do something
1031 | case .b, .c:
1032 | // Do something else.
1033 | }
1034 | ```
1035 |
1036 |
1037 |
1038 | * (link) **Check for nil rather than using optional binding if you don't need to use the value.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#unused-optional-binding)
1039 |
1040 |
1041 |
1042 | #### Why?
1043 | Checking for nil makes it immediately clear what the intent of the statement is. Optional binding is less explicit.
1044 |
1045 | ```swift
1046 | var thing: Thing?
1047 |
1048 | // WRONG
1049 | if let _ = thing {
1050 | doThing()
1051 | }
1052 |
1053 | // RIGHT
1054 | if thing != nil {
1055 | doThing()
1056 | }
1057 | ```
1058 |
1059 |
1060 |
1061 | **[⬆ back to top](#table-of-contents)**
1062 |
1063 | ## File Organization
1064 |
1065 | * (link) **Alphabetize module imports at the top of the file a single line below the last line of the header comments. Do not add additional line breaks between import statements.** [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#sortedImports)
1066 |
1067 |
1068 |
1069 | #### Why?
1070 | A standard organization method helps engineers more quickly determine which modules a file depends on.
1071 |
1072 | ```swift
1073 | // WRONG
1074 |
1075 | // Copyright © 2018 Airbnb. All rights reserved.
1076 | //
1077 | import DLSPrimitives
1078 | import Constellation
1079 | import Epoxy
1080 |
1081 | import Foundation
1082 |
1083 | //RIGHT
1084 |
1085 | // Copyright © 2018 Airbnb. All rights reserved.
1086 | //
1087 |
1088 | import Constellation
1089 | import DLSPrimitives
1090 | import Epoxy
1091 | import Foundation
1092 | ```
1093 |
1094 |
1095 |
1096 | _Exception: `@testable import` should be grouped after the regular import and separated by an empty line._
1097 |
1098 |
1099 |
1100 | ```swift
1101 | // WRONG
1102 |
1103 | // Copyright © 2018 Airbnb. All rights reserved.
1104 | //
1105 |
1106 | import DLSPrimitives
1107 | @testable import Epoxy
1108 | import Foundation
1109 | import Nimble
1110 | import Quick
1111 |
1112 | //RIGHT
1113 |
1114 | // Copyright © 2018 Airbnb. All rights reserved.
1115 | //
1116 |
1117 | import DLSPrimitives
1118 | import Foundation
1119 | import Nimble
1120 | import Quick
1121 |
1122 | @testable import Epoxy
1123 | ```
1124 |
1125 |
1126 |
1127 | * (link) **Limit empty vertical whitespace to one line.** Favor the following formatting guidelines over whitespace of varying heights to divide files into logical groupings. [](https://github.com/realm/SwiftLint/blob/master/Rules.md#vertical-whitespace)
1128 |
1129 | * (link) **Files should end in a newline.** [](https://github.com/realm/SwiftLint/blob/master/Rules.md#trailing-newline)
1130 |
1131 | **[⬆ back to top](#table-of-contents)**
1132 |
1133 | ## Objective-C Interoperability
1134 |
1135 | * (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 `@objc` on individual methods and variables as necessary rather than exposing all API on a class to Objective-C via `@objcMembers`.
1136 |
1137 |
1138 |
1139 | ```swift
1140 | class PriceBreakdownViewController {
1141 |
1142 | private let acceptButton = UIButton()
1143 |
1144 | private func setUpAcceptButton() {
1145 | acceptButton.addTarget(
1146 | self,
1147 | action: #selector(didTapAcceptButton),
1148 | forControlEvents: .TouchUpInside)
1149 | }
1150 |
1151 | @objc
1152 | private func didTapAcceptButton() {
1153 | // ...
1154 | }
1155 | }
1156 | ```
1157 |
1158 |
1159 |
1160 | **[⬆ back to top](#table-of-contents)**
1161 |
1162 | ## Contributors
1163 |
1164 | - [View Contributors](https://github.com/airbnb/swift/graphs/contributors)
1165 |
1166 | **[⬆ back to top](#table-of-contents)**
1167 |
1168 | ## Amendments
1169 |
1170 | 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.
1171 |
1172 | **[⬆ back to top](#table-of-contents)**
1173 |
--------------------------------------------------------------------------------