This episode is freely available thanks to the support of our subscribers

Subscribers get exclusive access to new and all previous subscriber-only episodes, video downloads, and 10% discount for team members. Become a Subscriber

We take a look at features like renaming, extracting expressions, extracting methods, and more.

00:06 Today we'll have a look at the cool new refactoring features of Xcode 9. Back in the Objective-C days, we actually used AppCode's refactoring a lot. It changed the way we wrote our code: instead of copying and pasting lines, we used the refactoring system to extract expressions and methods and to create variables and properties.

00:52 Now with Xcode 9, we're getting a glimpse of what this way of working can look like. But the refactoring features are still in beta, so they don't always work perfectly. Therefore, it's good to have your project under version control.

Renaming

01:19 The most important shortcut to remember is Cmd+Shift+A, which brings up the new Actions menu at the current cursor position:

01:38 When we choose the Rename option, Xcode shows all the places where ViewController is used or mentioned, including the filename and the storyboard reference. It even shows that the file header's comment block mentions ViewController. This mention of the name in the comment block is initially greyed out, but when we click it, it'll also be replaced.

02:07 Now we can type a new name, like PlayerViewController. When we build, we find out something went wrong: Xcode updated the file reference in the project, but it failed to rename the actual file. This shows that the system isn't completely stable yet.

Extracting Expressions

03:04 Another thing Xcode can do is extract expressions. Let's say we have the following statement:

if view.subviews.count > 0 {

}

03:26 Later we might decide we need to use the subview count in more than one place, so it'd be good to extract it to a separate expression. Unfortunately, the shortcut Cmd+Shift+A works only for the current identifier, view, and not the whole expression. Instead, we have to select the entire expression, view.subviews.count, and choose the menu option Editor > Refactor > Extract Expression. This results in the following:

let extractedExpr: Int = view.subviews.count
if extractedExpr > 0 {

}

03:58 And of course, we can then quickly rename the expression to viewCount using the Actions menu again:

let viewCount: Int = view.subviews.count
if viewCount > 0 {

}

04:19 If we prefer to only use the keyboard to extract the expression, we could select the expression and press Cmd+Shift+?. This opens the Help menu search, in which we can simply start typing "extract" and then navigate to the correct menu button using the arrow keys. This Help menu shortcut works universally on macOS.

05:01 As an even quicker alternative to using the Help menu, we can assign a custom shortcut. In Xcode's preferences, under Key Bindings, we can search for "extract expression" and assign a unique shortcut, say Cmd+Option+Ctrl+E. This is handy if we use the menu option a lot.

Various Useful Actions

06:17 By pressing Cmd+Shift+A on an if-statement, we can quickly add else or else if statements:

06:55 There's also more support for working with enums. When we write a switch statement for an enum, we can wait for the compiler error saying that the switch must be exhaustive:

class PlayerViewController: UIViewController {
    enum PlayState {
        case playing(title: String)
        case stopped
    }

    var state: PlayState = .stopped

    override func viewDidLoad() {
        super.viewDidLoad()

        switch state {

        }
    }
}

07:20 The compiler error now comes with a fix-it to add the missing cases. It sometimes even adds the variable binding — for title in this case:

switch state {
case . playing(let title):
    // ...
case .stopped:
    // ...
}

08:00 If we add a new case to the enum, we can use the fix-it again.

08:21 You can also use the Cmd+Shift+A shortcut on the switch statement itself. This gives you more naive options to add a blank case or a default, without any knowledge of your enum.

09:07 Another useful bit is that you can press the Cmd key and hover over your code with the mouse. This highlights the scope, which makes the structure of your code visible. When clicking with the Cmd key pressed, you get the same Actions menu as with the shortcut Cmd+Shift+A.

Extracting Methods

09:44 If we select our entire switch statement, we can extract a method using the Refactor menu:

fileprivate func extractedFunc() {
    switch state {
    case . playing(let title):
        print("Playing: \(title)"
    case .stopped:
        print("Stopped")
    case .paused:
        print("Paused")
    }
}

override func viewDidLoad() {
    // ...
    extractedFunc()
}

10:11 Xcode moved the switch statement into a new method, extractedFunc, and added a call to this method at the line where the statement used to be. Of course, we can easily rename the method to printState via refactoring.

10:46 It's interesting how method extraction deals with local variables. Let's say that instead of printing the state, we want to work with a variable. We undo the last changes and add a result variable:

override func viewDidLoad() {
        // ...
        var result: String = ""
        switch state {
        case .playing(let title):
            result = "Playing: \(title)"
        case .stopped:
            result = "Stopped"
        case .paused:
            result = "Paused"
        }
    }

11:16 If we extract the switch statement into a method, Xcode creates an inout parameter:

fileprivate func extractedFunc(_ result: inout String) {
    switch state {
    case .playing(let title):
        result = "Playing: \(title)"
    case .stopped:
        result = "Stopped"
    case .paused:
        result = "Paused"
    }
}

override func viewDidLoad() {
    // ...
    var result: String = ""
    extractedFunc(&result)
}

11:22 It makes sense that Xcode uses an inout parameter, since we're writing to the result variable. Xcode doesn't make unnecessary copies; it just writes to the variable in place.

11:57 In the future, maybe we'll have a way to let the compiler refactor the code to result = extractedFunc(). We can also try to add this custom refactoring ourselves in a future episode.