├── .gitignore ├── LICENSE └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jonathan Wight 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 | 23 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Swift-Best-Practices 2 | 3 | Best practices for software development with Swift. 4 | 5 | ## Preface 6 | 7 | This document grew from a set of notes I produced while working on [SwiftGraphics][SwiftGraphics]. Most of the recommendations in this guide are definitely considered opinions and arguments could be made for other approaches. That's fine. When other approaches make sense they should be presented in addition. 8 | 9 | These best practices do not dictate or recommend whether Swift should be used in a procedural, object-oriented or functional manner. Instead a pragmatic approach is taken. Individual recommendations might be focused on object-oriented or functional solutions as needed. 10 | 11 | The scope of this document is mostly aimed at the Swift language and Swift standard library. That said specific recommendations on how to use Swift with Mac OS, iOS, WatchOS and TVOS might be provided if a unique Swift angle or insight can be provided. Hints & tips style recommendations on how to use Swift effectively with Xcode and LLDB might also be provided. 12 | 13 | This is very much a work in progress. Contributions are very much appreciated in the form of pull requests or filing of issues. 14 | 15 | Discussion can be found on the [Swift-Lang slack][slack] (in the #bestpractices channel) 16 | 17 | ### Note to Contributors 18 | 19 | Please make sure all examples are runnable (which may not be the case for existing examples). This markdown will be converted to a Mac OS X playground. 20 | 21 | ### Golden Rules 22 | 23 | * Apple is generally right. Defer to Apple's preferred or demonstrated way of doing things. You should follow the style of Apple's code as defined within their “[The Swift Programming Language][Swift_Programming_Language]” book wherever possible. However Apple is a large corporation and be prepared to see discrepancies in their example code. 24 | * Never write code merely to attempt to reduce the number of keystrokes you need to type. Rely on autocompletion, autosuggestion, copy and paste, etc instead. Verbosity is often helpful to other maintainers of your code. That said, being overly verbose can bypass one of Swift's key benefits: type inference. 25 | 26 | ## Best Practices 27 | 28 | ### Naming 29 | 30 | As per the “[Swift Programming Language][Swift_Programming_Language]” type names should be [upper camel case][Studly_caps] (example: “`VehicleController`”). 31 | 32 | [Studly_caps]: https://en.wikipedia.org/wiki/Studly_caps 33 | 34 | Variables and constants should be lower camel case (example “`vehicleName`”). 35 | 36 | You should use Swift modules to namespace your code and not use Objective-C style class prefixes for Swift code (unless of course interfacing with Objective-C). 37 | 38 | Do not use any form of [Hungarian notation][Hungarian_notation] (e.g. k for constants, m for methods), instead use short concise names and use Xcode's type Quick Help (⌥ + click) to discover a variable's type. Similarly do not use [`SNAKE_CASE`][Snake_case]. 39 | 40 | [Hungarian_notation]: https://en.wikipedia.org/wiki/Hungarian_notation 41 | [Snake_case]: https://en.wikipedia.org/wiki/Snake_case 42 | 43 | The only exception to this general rule are enum values, which should be uppercase (this follows Apple's "[Swift Programming Language][Swift_Programming_Language]" style): 44 | 45 | ```swift 46 | enum Planet { 47 | case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune 48 | } 49 | ``` 50 | 51 | Needless contractions and abbreviations should be avoided where at all possible, you can actually type out the characters "ViewController" without any harm and rely on Xcode's autocompletion to save you typing in the future. Extremely common abbreviations such as URL are fine. Abbreviations should be represented all uppercase ("URL") or all lowercase "url" as appropriate. Use the same rule for types and variables; if url was a type it would be uppercase, if url was a variable it would be lower case. 52 | 53 | ### Comments 54 | 55 | Comments should _not_ be used to disable code. Commented out code is dead code and pollutes your source. If you want to remove code but keep it around in case it's useful in the future you should be relying on git and/or your bug tracker. 56 | 57 | (TODO: Add section about doc comments with link to nshipster) 58 | 59 | ### Type inference 60 | 61 | Where possible, use Swift’s type inference to help reduce redundant type information. For example, prefer: 62 | 63 | ```swift 64 | var currentLocation = Location() 65 | ``` 66 | 67 | to: 68 | 69 | ```swift 70 | var currentLocation: Location = Location() 71 | ``` 72 | 73 | ### Self Inference 74 | 75 | Let the compiler infer self in all cases where it is able to. Areas where self should be explicitly used includes setting parameters in init, and non-escaping closures. For example: 76 | 77 | ```swift 78 | struct Example { 79 | let name: String 80 | 81 | init(name: String) { 82 | self.name = name 83 | } 84 | } 85 | ``` 86 | 87 | ### Parameter List Inference 88 | 89 | Specifying parameter types inside a closure expression can lead to rather verbose code. Only specify types if needed. 90 | 91 | ```swift 92 | let people = [ 93 | ("Mary", 42), 94 | ("Susan", 27), 95 | ("Charlie", 18), 96 | ] 97 | 98 | let strings = people.map() { 99 | (name: String, age: Int) -> String in 100 | return "\(name) is \(age) years old" 101 | } 102 | ``` 103 | 104 | If at all possible remove the types if the compiler can infer them: 105 | 106 | ```swift 107 | let strings = people.map() { 108 | (name, age) in 109 | return "\(name) is \(age) years old" 110 | } 111 | ``` 112 | 113 | Using the numbered parameter names ("`$0`") further reduces verbosity, often eliminating the parameter list completely. Only use the numbered form when the parameter names add no further information to the closure (e.g. very simple maps and filters). 114 | 115 | Apple can and will change the parameter types of closures provided by their Swift "conversion" of Objective-C frameworks. For example, optionals are removed or changed to auto-unwrapping etc. Intentionally under-specifying your optionals and relying on Swift to infer the types, reduces the risk of the code breaking under these circumstances. 116 | 117 | You should almost always refrain from specifying the return type. For example this parameter list is completely redundant: 118 | 119 | ```swift 120 | dispatch_async(queue) { 121 | () -> Void in 122 | print("Fired.") 123 | } 124 | ``` 125 | 126 | ### Constants 127 | 128 | Constants used within type definitions should be declared static within a type. For example: 129 | 130 | ```swift 131 | struct PhysicsModel { 132 | static var speedOfLightInAVacuum = 299_792_458 133 | } 134 | 135 | class Spaceship { 136 | static let topSpeed = PhysicsModel.speedOfLightInAVacuum 137 | var speed: Double 138 | 139 | func fullSpeedAhead() { 140 | speed = Spaceship.topSpeed 141 | } 142 | } 143 | ``` 144 | 145 | Making the constants static allow them to be referred to without needing instances of the type. 146 | 147 | Constants at global level should generally be avoided except for singletons. 148 | 149 | ### Computed Properties 150 | 151 | Use the short version of computed properties if you only need to implement a getter. For example, prefer this: 152 | 153 | ```swift 154 | class Example { 155 | var age: UInt32 { 156 | return arc4random() 157 | } 158 | } 159 | ``` 160 | 161 | to this: 162 | 163 | ```swift 164 | class Example { 165 | var age: UInt32 { 166 | get { 167 | return arc4random() 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | ### Converting Instances 174 | 175 | When creating code to convert instances from one type to another, use `init()` methods: 176 | 177 | ```swift 178 | extension NSColor { 179 | convenience init(_ mood: Mood) { 180 | super.init(color: NSColor.blueColor) 181 | } 182 | } 183 | ``` 184 | Init methods now seem to be the preferred manner to convert instances of one type to another in the Swift Standard Library. 185 | 186 | "to" methods are another reasonable technique (although you should follow Apple's lead and use init methods): 187 | 188 | ```swift 189 | struct Mood { 190 | func toColor() -> NSColor { 191 | return NSColor.blueColor() 192 | } 193 | } 194 | ``` 195 | 196 | While you might be tempted to use a getter, e.g: 197 | 198 | ```swift 199 | struct Mood { 200 | var color: NSColor { 201 | return NSColor.blueColor() 202 | } 203 | } 204 | ``` 205 | 206 | getters should generally be limited to returning components of the receiving type. For example returning the area of a `Circle` instance is well suited to be a getter, but converting a `Circle` to a `CGPath` is better as a "to" function or an `init()` extension on `CGPath`. 207 | 208 | ### Singletons 209 | 210 | Singletons are simple in Swift: 211 | 212 | ```swift 213 | class ControversyManager { 214 | static let sharedInstance = ControversyManager() 215 | } 216 | ``` 217 | 218 | The Swift runtime will make sure that the singleton is created and accessed in a thread-safe manner. 219 | 220 | Singletons should generally just be accessed via "`sharedInstance`" static property unless you have a compelling reason to name it otherwise. Do not use static functions or global functions to access your singleton. 221 | 222 | (Because singletons are so easy in Swift and because consistent naming saves you so much time you will have even more time to complain about how singletons are an anti-pattern and should be avoided at all costs. Your fellow developers will thank you.) 223 | 224 | ### Extensions for Code Organisation 225 | 226 | Extensions should be used to help organise code. 227 | 228 | Methods and properties that are peripheral to an instance should be moved to an extension. Note that, currently not all property types can be moved to an extension - do the best you can within this limitation. 229 | 230 | You should use extensions to help organise your instance definitions. One good example of this is a view controller that implements table view data source and delegate protocols. Instead of mixing all that table view code into one class, put the data source and delegate methods onto extensions that adopt the relevant protocol. 231 | 232 | Inside a single source file feel free to break down a definition into whatever extensions you feel best organise the code in question. Don't worry about methods in the main class or struct definition referring to methods or properties inside extensions. As long as it is all contained within one Swift file it is all good. 233 | 234 | Conversely, the main instance definition should not refer to elements defined in extensions outside of the main Swift file. 235 | 236 | ### Chained Setters 237 | 238 | Do not use chained methods as a more "convenient" replacement for simple property setters: 239 | 240 | Prefer: 241 | 242 | ```swift 243 | instance.foo = 42 244 | instance.bar = "xyzzy" 245 | ``` 246 | 247 | to: 248 | 249 | ```swift 250 | instance.setFoo(42).setBar("xyzzy") 251 | ``` 252 | 253 | Traditional setters are far easier and require far less boilerplate code than chain-able setters. 254 | 255 | ### Error Handling 256 | 257 | Swift 2's `do`/`try`/`catch` mechanism is fantastic. Use it. (TODO: elaborate and provide examples) 258 | 259 | ### Avoid `try!` 260 | 261 | In general prefer: 262 | 263 | ```swift 264 | do { 265 | try somethingThatMightThrow() 266 | } 267 | catch { 268 | fatalError("Something bad happened.") 269 | } 270 | ``` 271 | 272 | to: 273 | 274 | ```swift 275 | try! somethingThatMightThrow() 276 | ``` 277 | 278 | Even though this form is far more verbose it provides context to other developers reviewing the code. 279 | 280 | It is okay to use `try!` as a temporary error handler until a more comprehensive error handling strategy is evolved. But it is suggested you periodically sweep your code for any errant `try!` that might have snuck past your code reviews. 281 | 282 | ### Avoid `try?` where possible 283 | 284 | `try?` is used to "squelch" errors and is only useful if you truly don't care if the error is generated. In general though, you should catch the error and at least log the failure. 285 | 286 | ### Avoid `!` where possible 287 | 288 | In general prefer `if let`, `guard let`, and `assert` to `!`, whether as a type, a property/method chain, `as!`, or (as noted above) `try!`. It’s better to provide a tailored error message or a default value than to crash without explanation. Design with the possibility of failure in mind. 289 | 290 | As an author, if you do use `!`, consider leaving a comment indicating what assumption must hold for it to be used safely, and where to look if that assumption is invalidated and the program crashes. Consider whether that assumption could reasonably be invalidated in a way that would leave the now-invalid `!` unchanged. 291 | 292 | As a reviewer, treat `!` with skepticism. 293 | 294 | ### Early Returns & Guards 295 | 296 | When possible, use `guard` statements to handle early returns or other exits (e.g. fatal errors or thrown errors). 297 | 298 | Prefer: 299 | 300 | ```swift 301 | guard let safeValue = criticalValue else { 302 | fatalError("criticalValue cannot be nil here") 303 | } 304 | someNecessaryOperation(safeValue) 305 | ``` 306 | 307 | to: 308 | 309 | ```swift 310 | if let safeValue = criticalValue { 311 | someNecessaryOperation(safeValue) 312 | } else { 313 | fatalError("criticalValue cannot be nil here") 314 | } 315 | ``` 316 | 317 | or: 318 | 319 | ```swift 320 | if criticalValue == nil { 321 | fatalError("criticalValue cannot be nil here") 322 | } 323 | someNecessaryOperation(criticalValue!) 324 | ``` 325 | 326 | This flattens code otherwise tucked into an `if let` block, and keeps early exits near their relevant condition instead of down in an `else` block. 327 | 328 | Even when you're not capturing a value (`guard let`), this pattern enforces the early exit at compile time. In the second `if` example, though code is flattened like with `guard`, accidentally changing from a fatal error or other return to some non-exiting operation will cause a crash (or invalid state depending on the exact case). Removing an early exit from the `else` block of a `guard` statement would immediately reveal the mistake. 329 | 330 | ### "Early" Access Control 331 | 332 | Even if your code is not broken up into independent modules, you should always be thinking about access control. Marking a definition as "private" or "internal" can act as lightweight documentation for your code. Anyone reading the code will know that these elements are "hands off". Conversely, marking a definition as "public" is an invite for other code to access the marked elements. It is best to be explicit and not rely on Swift's default access control level ("internal"). 333 | 334 | If your codebase grows in the future, it may end being broken down into sub-modules. Doing so on a codebase already decorated with access control information is much quicker and easier. 335 | 336 | ### "Restrictive" Access Control 337 | 338 | It is generally better to be overly restrictive when adding access control to your code. Where it makes sense prefer "private" definitions to "internal", and prefer "internal" to "public" (note: "internal" is the default). 339 | 340 | It is far easier to change the access control of your code to be more permissive later (along the spectrum: "private" to "internal" to "public") as needed. Code that has too permissive access control might be used inappropriately by other code. Making code more restrictive could involve finding the inappropriate or incorrect uses and providing better interfaces. This is a trying to close the stable door after the horse has bolted style problem. An example of this could be a type exposing an internal cache publicly. 341 | 342 | Furthermore, restricting access to code limits the "exposed surface area" and allows the code to be refactored with less chance of impacting other code. Other techniques such as "Protocol Driven Development" can also help. 343 | 344 | ## TODO Section 345 | 346 | This is a list of headings for possible future expansion. 347 | 348 | ### Protocols & Protocol Driven Development 349 | 350 | ### Reference vs Value Types 351 | 352 | ### Async Closures 353 | 354 | ### `unowned` vs `weak` 355 | 356 | ### Cocoa Delegates 357 | 358 | ### Immutable Structs 359 | 360 | ### Instance Initialisation 361 | 362 | ### Logging & Printing 363 | 364 | ### Computed Properties vs Functions 365 | 366 | ### Value Types and Equality 367 | 368 | [SwiftGraphics]: https://github.com/schwa/SwiftGraphics/blob/develop/Documentation/Notes.markdown 369 | [slack]: http://swift-lang.schwa.io 370 | [Swift_Programming_Language]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html 371 | --------------------------------------------------------------------------------