├── 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.** [![SwiftLint: some_rule](https://img.shields.io/badge/SwiftLint-some__rule-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#some_rule) [![SwiftFormat: some_rule](https://img.shields.io/badge/SwiftFormat-some__rule-7B0051.svg)](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.** [![SwiftFormat: trailingSpace](https://img.shields.io/badge/SwiftFormat-trailingSpace-7B0051.svg)](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.** [![SwiftLint: type_name](https://img.shields.io/badge/SwiftLint-type__name-007A87.svg)](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.** [![SwiftFormat: redundantSelf](https://img.shields.io/badge/SwiftFormat-redundantSelf-7B0051.svg)](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.** [![SwiftFormat: trailingCommas](https://img.shields.io/badge/SwiftFormat-trailingCommas-7B0051.svg)](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.** [![SwiftLint: legacy_constructor](https://img.shields.io/badge/SwiftLint-legacy__constructor-007A87.svg)](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.** [![SwiftLint: legacy_cggeometry_functions](https://img.shields.io/badge/SwiftLint-legacy__cggeometry__functions-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-cggeometry-functions) [![SwiftLint: legacy_constant](https://img.shields.io/badge/SwiftLint-legacy__constant-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#legacy-constant) [![SwiftLint: legacy_nsgeometry_functions](https://img.shields.io/badge/SwiftLint-legacy__nsgeometry__functions-007A87.svg)](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.** [![SwiftLint: colon](https://img.shields.io/badge/SwiftLint-colon-007A87.svg)](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.** [![SwiftLint: return_arrow_whitespace](https://img.shields.io/badge/SwiftLint-return__arrow__whitespace-007A87.svg)](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.** [![SwiftFormat: redundantParens](https://img.shields.io/badge/SwiftFormat-redundantParens-7B0051.svg)](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.** [![SwiftLint: empty_enum_arguments](https://img.shields.io/badge/SwiftLint-empty__enum__arguments-007A87.svg)](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.** [![SwiftLint: redundant_void_return](https://img.shields.io/badge/SwiftLint-redundant__void__return-007A87.svg)](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. [![SwiftLint: void_return](https://img.shields.io/badge/SwiftLint-void__return-007A87.svg)](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 (`_`).** [![SwiftLint: unused_closure_parameter](https://img.shields.io/badge/SwiftLint-unused__closure__parameter-007A87.svg)](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.** [![SwiftLint: closure_spacing](https://img.shields.io/badge/SwiftLint-closure__spacing-007A87.svg)](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`). [![SwiftLint: operator_usage_whitespace](https://img.shields.io/badge/SwiftLint-operator__usage__whitespace-007A87.svg)](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. [![SwiftLint: implicitly_unwrapped_optional](https://img.shields.io/badge/SwiftLint-implicitly__unwrapped__optional-007A87.svg)](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. [![SwiftLint: redundant_string_enum_value](https://img.shields.io/badge/SwiftLint-redundant__string__enum__value-007A87.svg)](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. [![SwiftLint: fatal_error_message](https://img.shields.io/badge/SwiftLint-fatal__error__message-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#fatal-error-message) [![SwiftLint: force_cast](https://img.shields.io/badge/SwiftLint-force__cast-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#force-cast) [![SwiftLint: force_try](https://img.shields.io/badge/SwiftLint-force__try-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#force-try) [![SwiftLint: force_unwrapping](https://img.shields.io/badge/SwiftLint-force__unwrapping-007A87.svg)](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.** [![SwiftLint: unused_optional_binding](https://img.shields.io/badge/SwiftLint-unused_optional_binding-007A87.svg)](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.** [![SwiftFormat: sortedImports](https://img.shields.io/badge/SwiftFormat-sortedImports-7B0051.svg)](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. [![SwiftLint: vertical_whitespace](https://img.shields.io/badge/SwiftLint-vertical__whitespace-007A87.svg)](https://github.com/realm/SwiftLint/blob/master/Rules.md#vertical-whitespace) 1128 | 1129 | * (link) **Files should end in a newline.** [![SwiftLint: trailing_newline](https://img.shields.io/badge/SwiftLint-trailing__newline-007A87.svg)](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 | --------------------------------------------------------------------------------