Blog

Latest Post

Swift Tip: An Example Refactoring

In last week’s tip, we showed you a typical refactoring from our server-side Swift project, as we continue to port the Swift Talk backend from Ruby to Swift. This week, we follow with another useful refactoring.

To fetch all episodes for a Collection, we have a simple method in an extension. Likewise, we have a computed property that calculates the total duration of all the episodes in the collection:

extension Collection {
    func episodes() -> [Episode] {
        return Episode.all.filter { $0.collections.contains(id) }
    }

    var totalDuration: TimeInterval {
        return episodes.map { $0.mediaDuration }.reduce(0, +)
    }
}

These implementations aren’t finished; they don’t take the role of the user into account. For example, we want to hide the unreleased episodes from regular users, and only show them for administrators.

We refactored the episodes() method like this:

extension Collection
    func episodes(for user: UserData?) -> [Episode] {
        return Episode.scoped(for: user).filter { $0.collections.contains(id) }
    }
}

We also need to change the totalDuration from a computed property to a method:

extension Collection {
    func totalDuration(for user: UserData?) -> TimeInterval {
        return episodes(for: user).map { $0.mediaDuration }.reduce(0,+)
    }
}

However, it feels slightly wrong to pass on the user directly. What about other computed properties that depend on episodes? After thinking about it, we removed totalDuration(for:) from our extension, and rewrote it like this:

extension Sequence where Element == Episode {
    var totalDuration: TimeInterval {
        return map { $0.mediaDuration }.reduce(0, +)
    }
}

This changes our call site: instead of collection.totalDuration(for: user), we have to write collection.episodes(for: user).totalDuration.

The nice thing about this implementation is that we can now reuse the functionality in other places — for example, we can use it when we show the total duration of all episodes on Swift Talk. We can also easily add more computed properties to the constrained extension on Sequence without having to pass on the user everywhere.

If you like these kind of refactorings, check out Swift Talk, our weekly live-coding video series. We do them often! 😊

Previous Posts

Swift Tip: Refactoring to Associated Values

We’re in the process of rewriting our Swift Talk backend from Ruby to Swift. Rather than trying to get everything right straight away, we typically port things over by writing Swift code with a very similar structure to the Ruby code. We make sure that everything works as expected, and only then do we refactor the code to be more Swift-like.

As an example, on the Episode page we show the user one of four things:

  • “Become a subscriber to download episode videos.”
  • “Re-downloading episodes doesn’t use any download credits.”
  • “You have (creditsLeft) download credits left”
  • “No download credits left. You’ll get new credits at the next billing cycle.”

In Ruby, the four cases were modeled as an enum, and we did the same in Swift:

enum DownloadStatus {
  case notSubscribed
  case reDownload
  case canDownload
  case noCredits
}

To generate the text above, we can write an extension on DownloadStatus:

extension DownloadStatus {
  func text(creditsLeft: Int) -> String {
    switch self {
       // ...
    }
  }
}

This works as expected, but after using it for a while we realized that passing on the creditsLeft everywhere — from the database, to the “controller” layer, to the view, to this method — can be a little annoying. It’s also a little annoying to make up a value for the notSubscribed case. In fact, the only place where we need it is in the canDownload case.

To remedy these annoyances, we refactored our DownloadStatus enum to include an associated value:

enum DownloadStatus {
        case notSubscribed
        case reDownload
        case canDownload(creditsLeft: Int)
        case noCredits
}

With this in place, we don’t have to pass on the creditsLeft separately, we can include it directly in the canDownload case. This also makes it possible to write text(creditsLeft:) as a computed property text:

var text: String {
  switch self {
    // ...
  }
}

Changing our code to use associated values gives us clearer code, and more importantly, instead of passing two parameters around in multiple places we now embed the second parameter in the DownloadStatus enum.

To learn more about enums, take a look at our books: Functional Swift shows how enums can help you write simpler code, and Advanced Swift gives a more in-depth account of how they function.

You can buy both books as part of the Swift Essentials bundle, with a little discount to say thank you. 🙏

Swift Tip: Using AppKit from the Command-line

In Swift, it’s very easy to write a command-line app. As a simple example, you can paste the following into a file called wc.swift:

while let line = readLine() {
    print(line.split(separator: " ").count)
}

This will print out the number of words on each line. We can, for instance, use it on itself:

$ cat wc.swift | swift wc.swift
6
3
1

On the Mac, we’re not limited to console input and output. We can also run an entire Mac app directly from the command-line. When we import AppKit, we can access the shared application, set a delegate, and run the application.

For example, let’s save the following code into a file called spell.swift:

import AppKit

let app = NSApplication.shared

class AppDelegate: NSObject, NSApplicationDelegate {
    let window = NSWindow(contentRect: NSMakeRect(200, 200, 400, 200),
                          styleMask: [.titled, .closable, .miniaturizable, .resizable],
                          backing: .buffered,
                          defer: false,
                          screen: nil)

    func applicationDidFinishLaunching(_ notification: Notification) {
        window.makeKeyAndOrderFront(nil)
        let field = NSTextView(frame: window.contentView!.bounds)
        field.backgroundColor = .white
        field.isContinuousSpellCheckingEnabled = true
        window.contentView?.addSubview(field)
        DispatchQueue(label: "background").async {
            while let str = readLine(strippingNewline: false) {
                DispatchQueue.main.async {
                    field.textStorage?.append(NSAttributedString(string: str))
                }
            }
            app.terminate(self)
        }
    }
}

let delegate = AppDelegate()
app.delegate = delegate
app.run()

From the terminal, we can now run swift spell.swift, and the app will read lines from the standard input, adding them to a text field that has spell checking enabled. This particular example might be silly, but with all of AppKit at our disposal the possibilities are practically endless.

For instance, we could use the Open panel to prompt the user for a file:

// select.swift
import AppKit

func selectFile() -> URL? {
    let dialog = NSOpenPanel()
    dialog.allowedFileTypes = ["jpg", "png"]
    guard dialog.runModal() == .OK else { return nil }
    return dialog.url
}

print(selectFile()?.absoluteString ?? "")

Similarly, we could write scripts with a GUI for previewing images, processing images, finding a web page’s URL using WKWebView, and so on.

In other words, we can easily sprinkle some GUI elements onto our command-line scripts when it’s more convenient than relying on the command-line alone.

If you’d like to learn more, Swift Talk Episode 22 shows you how to write simple command-line tools that leverage frameworks like Foundation and Cocoa.

To watch, become a subscriber. 🙂

Swift Tip: Mixing and Matching Imperative and Functional Code

Swift is a hybrid language: it knows all the typical imperative control flow statements and supports mutable variables and properties; at the same time, Swift also supports typical functional programming features, like value types and first class functions.

In Swift, we often have the choice: do we want to express a piece of logic in an imperative or in a functional way?

As a simple example, we’ll sum up the total width of an array of views:

var width: CGFloat = 0
for view in subviews {
    width += view.frame.width
}

This is a straightforwardly imperative way to express this logic. However, we could also choose the functional alternative:

let width = subviews.reduce(0) { result, view in
    result + view.frame.width
}

Both snippets do the same thing, with one small difference: in the imperative case we’re left with a mutable width variable after the calculation is done, whereas we have an immutable width variable in the functional case.

To make the code a bit more interesting, we’ll try summing up the total width of the views where isHidden is false. Once the logic becomes more complex, the alternative ways of expressing it start to diverge.

Here’s the imperative version:

var width: CGFloat = 0
for view in subviews {
    guard !view.isHidden else { continue }
    width += view.frame.width
}

Of course, there are other ways of expressing this logic imperatively, for example using a where clause:

for view in subviews where !view.isHidden {
    width += view.frame.width
}

Let’s try a functional version that accomplishes the same thing. One option is to first filter out the hidden views and then use reduce to calculate the total width:

let visibleViews = subviews.filter { !$0.isHidden }
let width = visibleViews.reduce(0) { result, view in
    result + view.frame.width
}

Alternatively, we can include the isHidden check within the reduce function:

let width = subviews.reduce(0) { result, view in
    view.isHidden ? result : result + view.frame.width
}

A nice feature of the previous version — first filtering, then reducing — is that the reduce part of the logic is the same, no matter if we sum up the width of all views, only the even views, or any other subset of them. This sameness is a clear suggestion that the logic can be abstracted out, for example by using a computed property:

extension Array where Element == UIView {
    var totalWidth: CGFloat {
        return reduce(0) { result, view in result + view.frame.width }
    }
}

Now, the total width of the even views is simply:

let width = visibleViews.totalWidth

Once again, the implementation of this extension and the extraction of the visible views could also be written imperatively:

extension Array where Element == UIView {
    var totalWidth: CGFloat {
        var result: CGFloat = 0
        for view in self {
            result += view.frame.width
        }
        return result
    }
}

var visibleViews: [UIView] = []
for view in subviews where !view.isHidden {
    visibleViews.append(view)
}
let width = evenViews.totalWidth

Swift allows mixing and matching of imperative and functional programming techniques, and often which technique you prefer is a matter of taste. We use both in our own code; for some problems we naturally use map or reduce, for other problems we write an imperative loop and aggregate the result as we go along.

No matter which you choose, breaking your problem down into smaller parts, as we have done with the totalWidth extension above, is a very useful technique for both.

If you’d like to learn more about functional approaches in Swift, our book Functional Swift is a great place to start 🙂

Building a Form Library

Over the last few weeks we have completed a series of ten Swift Talk episodes following the process of building a declarative form library.

For those who haven’t been following along, we’d like to take this opportunity to revisit our original design goals and give you an idea of what this library is already capable of.

When we started to build the library, we had two major design goals in mind:

  • Forms should be written with a fully declarative syntax.
  • The library should synchronise the underlying data with the form’s UI automatically — a difficult task!

When using forms, we often have to update multiple UI elements in response to a change in the underlying data. This personal hotspot form is an easily recognised example:

Switching the hotspot on or off has a number of effects:

  1. It changes the personal hotspot status label on the main settings screen.
  2. It changes the section footer below the switch.
  3. It shows or hides the section containing the “Network Name” and “Password” fields.

Writing this logic by hand quickly becomes messy, and risks introducing errors.

To create this form with our library, we start by defining the underlying data structures:

struct Settings {
    var hotspot = Hotspot()

    var hotspotEnabled: String {
        return hotspot.isEnabled ? "On" : "Off"
    }
}

struct Hotspot {
    var isEnabled: Bool = true
    var password: String = "Password"
    var networkName: String = "My Network"
}

Now we specify the form’s UI using a declarative API that uses Swift’s key paths to bind the UI to the data. The hotspot screen is defined like this:

let hotspotForm: Form<Hotspot> =
    sections([
        section([
            controlCell(title: "Personal Hotspot", control: uiSwitch(keyPath: \.isEnabled))
        ], footer: \.enabledSectionTitle),
        section([
            nestedTextField(title: "Password", keyPath: \.password),
            nestedTextField(title: "Network Name", keyPath: \.networkName)
        ], isVisible: \.isEnabled)
    ])

The switch is bound to the isEnabled property, and the section footer below the switch is bound to the computed enabledSectionTitle property, which allows us to make the footer dependent on the hotspot’s enabled state. Furthermore, the whole second section is shown or hidden by binding its visibility to the isEnabled property.

The main settings form embeds the hotspot settings like this:

let settingsForm: Form<Settings> =
    sections([
        section([
            detailTextCell(title: "Personal Hotspot", keyPath: \.hotspotEnabled, form: bind(form: hotspotForm, to: \.hotspot))
        ])
    ])

The supplementary label on the right-hand side is bound to the computed hotspotEnabled property, signalling the hotspot’s current state on the main screen without having to navigate into the hotspot settings screen.

If you’d like to learn more about this approach, check out the Building a Form Library Swift Talk series — the first three and the last two episodes are publicly available.

The complete series is available for subscribers.