├── items ├── scratch.md ├── use-map-for-transforming-values-and-nothing-else.md ├── prefer-let-over-var.md ├── prefer-enums-with-raw-values-over-constants.md └── create-focused-and-scoped-types.md └── README.md /items/scratch.md: -------------------------------------------------------------------------------- 1 | ### item: prefer struct to class 2 | 3 | Whenever appropriate, TODO. 4 | 5 | ### item: compose constant structs for immutability 6 | 7 | TODO: Tie in with immutability 8 | 9 | This bestows on the value the property of *immutability*, which has the following advantages: 10 | 11 | * Our principal goal as programmers is to reduce complexity. At any point in a program, we can reason about the state of a constant trivially -- its state is the state upon assignment. By contrast, the state of a variable can change endlessly. 12 | * With judicious use, immutable objects can lead to quicker execution speed and lower memory usage. The `hash` value of a `String`, the query parameters of an immutable URL, and the distance traced by an immutable sequence of points are all immutable as well. We can cache their values for later use instead of recompute them on every access. We can also share an immutable object with clients without requiring those clients to defensively copy it. 13 | * An immutable value is *safe* in many ways. For example, `String` is immutable and so its `hash` value is immutable. Consequently, it is safe to use as a `String` as a key in a `Dictionary`: The lower order bits of that `hash` will never change, and so an inserted key will never reside in the wrong bucket. An immutable value is also thread-safe, becuase a client must acquire a lock only to read data that another client can concurrently modify. An immutable value renders that moot. 14 | 15 | ### other items: 16 | 17 | * avoid implicitly unwrapped optionals 18 | * implement printable for value types 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Effective Swift 2 | 3 | Welcome to _Effective Swift_ -- an open source book that is modeled after the books _Effective C++_ and _Effective Java_. 4 | 5 | Herein you will find a collection of items, each specifying one general rule, to help you write Swift code more effectively. For each rule I've attempted to provide relevant example code -- most of which is currently in production. This book is not designed to teach you the basics of Swift; for that you should refer to its excellent [language guide](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html). Nor is it designed to teach you fundamental principles like abstraction and decomposition; for that you should refer to a tome like [Code Complete](http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670). 6 | 7 | Currently the items in this book are assorted. It is my goal to add new items and eventually organize them into chapters. I also expect to rewrite and even delete items, both as a consequence of the Swift community acclimating to the language and establishing best practices, and as a consequence of the language itself evolving. 8 | 9 | As a final note, _Effective C++_ and _Effective Java_ are two of my favorite technical books. I appreciate not only the soundness of their contents, but the brevity of their writing style. I hope that Effective Swift has the same value proposition. Please enjoy! 10 | 11 | ### Items 12 | 13 | * [Prefer `let` over `var`](items/prefer-let-over-var.md) 14 | * [Use `map` for transforming values and nothing else](items/use-map-for-transforming-values-and-nothing-else.md) 15 | * [Prefer enumerations with raw values over constants](items/prefer-enums-with-raw-values-over-constants.md) 16 | * [Create focused and scoped types](items/create-focused-and-scoped-types.md) 17 | 18 | -------------------------------------------------------------------------------- /items/use-map-for-transforming-values-and-nothing-else.md: -------------------------------------------------------------------------------- 1 | ### Use `map` for transforming values and nothing else 2 | 3 | The documentation for method `map` of type `Array` says: 4 | 5 | > Use this method to return a new array containing the results of applying a provided closure to transform each element in the existing array. 6 | 7 | And the closure parameter in its declaration is even named `transform`: 8 | 9 | ``` 10 | func map(transform: (T) -> U) -> [U] 11 | ``` 12 | 13 | Given an array of views, we can use `map` to materialize their heights in a new array: 14 | 15 | ```swift 16 | let viewHeights = views.map { view in 17 | view.frame.size.height 18 | } 19 | ``` 20 | 21 | Because the `map` method invokes a given closure for each element in an array, it may be tempting to use `map` as a generalized iteration construct. For example: 22 | 23 | ```swift 24 | views.map { view in 25 | view.frame.size.height *= 2 26 | } 27 | ``` 28 | 29 | But this code does not fulfill the expectation that `map` will transform the given array. Instead, this use of `map` performs a [side-effect](http://en.wikipedia.org/wiki/Side_effect_%28computer_science%29), namely doubling the height of each view. Any programmer who expects that each use of `map` only transforms arrays will be surprised that the behavior of the program is henceforth changed. 30 | 31 | Moreover, if we need the doubled height values, we might be tempted to write: 32 | 33 | ``` 34 | let doubledHeights = views.map { view in 35 | view.frame.size.height *= 2 36 | } 37 | ``` 38 | 39 | The Swift compiler generates neither a warning nor a error for this code. But the type of the returned array is not `[CGFloat]` as we might expect. It is instead `[Void]`, which is equivalent to `[()]`. 40 | 41 | #### Using `each` 42 | 43 | To avoid this abuse of `map`, we can extend `Array` with an `each`. Like `map`, it invokes a closure once for each element in the array. Unlike `map`, it does not return any value: 44 | 45 | ```swift 46 | extension Array { 47 | func each(applicator: Element -> Void) { 48 | for element in self { 49 | applicator(element) 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | The `Element` type refers to the `Element` typealias defined inside `Array`. The method itself borrows its name from its corresponding form in Ruby, as well as from the popular [jQuery](http://jquery.com/) and [Underscore](http://underscorejs.org/) libraries for JavaScript. 56 | 57 | A client can invoke `each` with a closure exactly like it would with `map`: 58 | 59 | ```swift 60 | views.each { view in 61 | view.frame.size.height *= 2 62 | } 63 | ``` 64 | 65 | Because the closure used by the `each` method cannot return a value, we conclude that it must process or consume its parameter -- thereby creating a side-effect. There is no ambiguity. Additionally, if the client attempts to assign the value returned by `each` to `doubledHeights` -- similar to what we did with `map` -- the Swift compiler will helpfully warn that the value is 66 | 67 | > inferred to have type '()', which may be unexpected 68 | 69 | -------------------------------------------------------------------------------- /items/prefer-let-over-var.md: -------------------------------------------------------------------------------- 1 | ### Prefer `let` over `var` 2 | 3 | Whenever possible, declare a value using `let` instead of using `var`, thereby declaring it as a constant instead of a variable. This makes its state easier to reason about: If the value is a `struct` -- including primitive types like `Int`, `String`, and containers like `Array` and `Dictionary` -- then the value is immutable throughout its lifetime. If the value is an instance of a `class`, then the value will refer to the same instance throughout its lifetime, albeit it may be mutable. (The use of `struct` vs `class` and their relation to immutability is explored more in future items.) 4 | 5 | #### Local values 6 | 7 | In Swift 1.0, you do not need to sacrifice `let` for `var` to cope with conditionals. For example, consider a `UserProgress` enum that specifies whether the user has not started, has started, or has finished watching a video. Given the length of a video in seconds and how many seconds the user has watched, we can compute the `UserProgress` value as: 8 | 9 | ```swift 10 | var progress = UserProgress.NotStarted 11 | if secondsWatched >= videoLength { 12 | progress = .Finished 13 | } else if secondsWatched > 0 { 14 | progress = .Started 15 | } 16 | ``` 17 | 18 | But if `progress` is not modified later, we should prefer to specify it as a constant using `let`. We can do so by factoring out the computation of `UserProgress` into a method: 19 | 20 | ```swift 21 | func progressForSecondsWatched( 22 | secondsWatched: NSTimeInterval, 23 | ofVideoWithLength videoLength: NSTimeInterval) -> UserProgress { 24 | if secondsWatched >= videoLength { 25 | return .Finished 26 | } else if secondsWatched > 0 { 27 | return .Started 28 | } else { 29 | return .NotStarted 30 | } 31 | } 32 | ``` 33 | 34 | We then invoke this method and assign its return value to `progress`: 35 | 36 | ```swift 37 | let progress = progressForSecondsWatched(secondsWatched, ofVideoWithLength: videoLength) 38 | ``` 39 | 40 | In Swift 1.2, the new rule is that a `let` constant must be initialized before it is used, but we do not need to initialize it upon declaration. We can forego factoring out the computation of `UserProgress` into a method, and instead write: 41 | 42 | ```swift 43 | let progress: UserProgress 44 | if (secondsWatched >= videoLength) { 45 | progress = .Finished 46 | } else if (secondsWatched > 0) { 47 | progress = .Started 48 | } else { 49 | progress = .NotStarted 50 | } 51 | ``` 52 | 53 | Note that factoring out a method anyway has other benefits, such as improved readability, the opportunity for reuse, and the ability to be tested in isolation. 54 | 55 | #### Static values 56 | 57 | Named constants are always preferable over [magic numbers](http://en.wikipedia.org/wiki/Magic_number_%28programming%29). In Swift 1.0, class constants are not yet supported, but a computed class property that returns a constant value can serve the same function: 58 | 59 | ```swift 60 | private class var fadeInAnimationsDuration: NSTimeInterval { 61 | return 0.5 62 | } 63 | ``` 64 | 65 | But Swift 1.2 supports class constants, so we can simply write: 66 | 67 | ```swift 68 | private static let fadeInAnimationsDuration: NSTimeInterval = 0.5 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /items/prefer-enums-with-raw-values-over-constants.md: -------------------------------------------------------------------------------- 1 | ### Prefer enumerations with raw values over constants 2 | 3 | In Objective-C, members of an enumeration have a unique integer value. Swift extends this idea by allowing you to define enumerations having a unique string, character, integer, or floating point type. These are called the *raw values* for the enumeration. 4 | 5 | Consider a `struct` named `UserExerciseFeedback` that pairs the unique identifier of an exercise with the user's feedback for that exercise: 6 | 7 | ```swift 8 | struct UserExerciseFeedback { 9 | let exerciseId: String 10 | let feedback: String 11 | } 12 | ``` 13 | 14 | The client may want to serialize an instance of this struct as a JSON object for delivery to the server. The keys of this object must be unique. We can enforce this uniqueness at compile-time by specifying the keys as raw values in an enumeration. This enumeration can belong to an extension that provides a `toJSON` method like so: 15 | 16 | ```swift 17 | extension UserExerciseFeedback { 18 | private enum JSONKey: String { 19 | case ExerciseID = "exercise_id" 20 | case Feedback = "feedback" 21 | } 22 | 23 | func toJSON() -> [String: AnyObject] { 24 | let json = [ 25 | JSONKey.ExerciseID.rawValue: exerciseId, 26 | JSONKey.Feedback.rawValue: feedback 27 | ] 28 | return json 29 | } 30 | } 31 | ``` 32 | 33 | If we chose not to use an enumeration to define the keys, but instead used a pair of as static constants, then no compilation error will catch the following careless copy-and-paste mistake: 34 | 35 | ```swift 36 | private static let exerciseIDJSONKey = "exercise_id" 37 | private static let feedbackJSONKey = "exercise_id" // error -- should be "feedback" 38 | ``` 39 | 40 | Therefore, prefer enumerations with raw values, and have the compiler catch such errors for you. 41 | 42 | #### Promote to enumeration values 43 | 44 | The `toJSON` method of the preceding example "demoted" the `JSONKey` abstraction to `String` because it was constructing a JSON object, and a JSON object must have keys that are `String` values. But whenever possible, "promote" a value of the raw type to its associated enumeration value by using the enumeration's `rawValue:` initializer. 45 | 46 | For example, consider `UIAlertView` and the `UIAlertViewDelegate` method `alertView(_:clickedButtonAtIndex:)`. Say the `UIAlertView` prompts the user to retry or cancel a request for data from the server. We can specify the indexes of these buttons as the raw values of an enumeration: 47 | 48 | ```swift 49 | enum RequestDataErrorButton: Int /* button index */ { 50 | case Cancel = 0 51 | case Retry = 1 52 | } 53 | ``` 54 | 55 | When `alertView(_:clickedButtonAtIndex:)` is invoked on the delegate, the `buttonIndex` parameter is an `Int`. We can promote it to its corresponding `RequestDataErrorButton` value by using the enumeration's `rawValue:` initializer, and then invoke `switch` on the enumeration value like so: 56 | 57 | ```swift 58 | func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { 59 | if let tappedButton = RequestDataErrorButton(rawValue: buttonIndex) { 60 | switch tappedButton { 61 | case .Cancel: 62 | cancelRequest() 63 | case .Retry: 64 | retryRequest() 65 | } else { 66 | println("Unrecognized button index: \(buttonIndex)") 67 | } 68 | } 69 | ``` 70 | 71 | Again, `tappedButton` is a higher level of abstraction than `buttonIndex`, because `tappedButton` represents the button itself. 72 | 73 | As in the preceding JSON example, using an enumeration allows the compiler to catch errors for us: If we add a new value to the `RequestDataErrorButton` enumeration, then the compiler will generate an error if we do not add a corresponding `case` to the `switch` statement. 74 | 75 | -------------------------------------------------------------------------------- /items/create-focused-and-scoped-types.md: -------------------------------------------------------------------------------- 1 | ### Create focused and scoped types 2 | 3 | Classes in Objective-C are heavyweight and so creating them is laborious. Typically you must specify: 4 | 5 | * the public `@interface` in its `.h` file 6 | * a class extension in its `.m` file defining any private properties or instance variables 7 | * the `@implementation` in its `.m` file 8 | 9 | Additionally, you may speicfy in a separate header file an additional class extension that declares methods to be called or overridden only by subclasses of your class. (An example of this rare practice is the `UIGestureRecognizerSubclass.h` header file for `UIGestureRecognizer`.) 10 | 11 | Swift follows the modern convention of allowing you to define a single file containing a single class, where we can specify an access level for the properties and methods of the class. The Swift blog explains [why there is no `protected` access level](https://developer.apple.com/swift/blog/?id=11); note that [favoring composition over inheritance](http://en.wikipedia.org/wiki/Composition_over_inheritance) renders this concern moot anyway. 12 | 13 | Consequently, types in Swift are lightweight and easy to create. Leverage this to create a suite of focused and scoped types. 14 | 15 | #### Focused 16 | 17 | For example, most problems on Khan Academy offer a sequence of hints if the user needs help. Tapping the hint button reveals the next hint in the sequence until all hints are exhausted. 18 | 19 | As for the visual treatment of the hint button, it is enabled as long as hints remain. Additionally, it only displays how many hints remain after the user has revealed the first hint. 20 | 21 | If we track the number of hints total and the number of hints remaining as `numberHintsTotal` and `numberHintsRemaining`, respectively, then we could write: 22 | 23 | ```swift 24 | hintsButton.enabled = (numberHintsRemaining > 0) 25 | 26 | if numberHintsTotal != numberHintsRemaining { 27 | hintsButton.numberHintsRemaining = numberHintsRemaining 28 | hintsButton.numberHintsLabelVisible = true 29 | } else { 30 | hintsButton.numberHintsLabelVisible = false 31 | } 32 | ``` 33 | 34 | To help ensure that we pass these two values together, we can easily create a `HintsCounter` structure that composes them. Moreover, we can add computed properties to `HintsCounter` that assign meaningful names to the conditional expressions above: 35 | 36 | ```swift 37 | struct HintsCounter { 38 | let numberTotal: UInt 39 | let numberRemaining: UInt 40 | 41 | var hasUsedHints: Bool { 42 | return (numberTotal != numberRemaining) 43 | } 44 | var hasRemainingHints: Bool { 45 | return (numberRemaining > 0) 46 | } 47 | } 48 | ``` 49 | 50 | Rewriting the code to use the `HintsCounter` instance: 51 | 52 | ```swift 53 | hintsButton.enabled = hintsCounter.hasRemainingHints 54 | 55 | if hintsCounter.hasUsedHints { 56 | hintsButton.numberHintsRemaining = hintsCounter.numberRemaining 57 | hintsButton.numberHintsLabelVisible = true 58 | } else { 59 | hintsButton.numberHintsLabelVisible = false 60 | } 61 | ``` 62 | 63 | The code now is now simpler and more readable. And although `HintsCounter` is very simple, it is now very easy to test its computed properties `hasUsedHints` and `hasRemainingHints`. Moreover, such an abstraction is ripe for reuse. For example, the following method to compute the accessibility label of the hint button uses both `hasUsedHints` and `hasRemainingHints` again: 64 | 65 | ```swift 66 | static func accessibilityLabelForHintButtonWithCounter(hintsCounter: HintsCounter) -> String { 67 | if hintsCounter.hasUsedHints { 68 | return hintsCounter.hasRemainingHints ? 69 | "Show the next hint" : "No hints remain" 70 | } else { 71 | return (hintsCounter.numberTotal > 0) ? 72 | "Show the first hint" : "No hints available" 73 | } 74 | } 75 | ``` 76 | 77 | Even this small `struct` pays big dividends. 78 | 79 | #### Scoped 80 | 81 | If one type is only meaningful within the context of another type, put the former within the scope of the latter, and set its access level appropriately. 82 | 83 | For example, after the user demonstrates proficiency in a skill, we display how many points the user earned, any badges the user earned, and the user's mastery level for the skill. A `CompletedSkillData` structure composes all of this data. Its inner structure `PointsBreakdown` specifies the multiple sources from which the user earned points: 84 | 85 | ```swift 86 | struct CompletedSkillData { 87 | struct PointsBreakdown { 88 | let pointsFromQuestions: Int 89 | let pointsFromBadges: Int 90 | let otherPoints: Int 91 | 92 | var totalPoints: Int { 93 | return pointsFromQuestions + pointsFromBadges + otherPoints 94 | } 95 | } 96 | 97 | let pointsBreakdown: PointsBreakdown 98 | let badges: [Badge] 99 | let masteryLevel: MasteryLevel 100 | } 101 | ``` 102 | 103 | The properties of `PointsBreakdown` represent only the user's points for a completed skill, and so a `PointsBreakdown` instance disassociated from the completed skill has no meaning. Therefore `PointsBreakdown` is an inner type of `CompletedSkillData`. 104 | 105 | Also consider the extension from the item _[prefer enumerations with raw values over constants](prefer-enums-with-raw-values-over-constants.md)_ that provides JSON encoding for the `UserExerciseFeedback` struct: 106 | 107 | ```swift 108 | extension UserExerciseFeedback { 109 | private enum JSONKey: String { 110 | case ExerciseID = "exercise_id" 111 | case Feedback = "feedback" 112 | } 113 | 114 | func toJSON() -> [String: AnyObject] { 115 | let json = [ 116 | JSONKeys.ExerciseID.rawValue: exerciseId, 117 | JSONKeys.Feedback.rawValue: feedback 118 | ] 119 | return json 120 | } 121 | } 122 | ``` 123 | 124 | The `UserExerciseFeedback` extension should encapsulate encoding an instance to JSON, and the keys of the returned dictionary are an implementation detail. Therefore the `JSONKey` enumeration is a private inner type of `UserExerciseFeedback`. 125 | 126 | --------------------------------------------------------------------------------