Articles, podcasts and news about Swift development, by John Sundell.

Swift playgrounds tips and tricks

Published on 23 Dec 2018

When Swift was first introduced in 2014, one of the most interesting developer tools that was launched along with it was Xcode’s new “Playground” feature. Designed to be an easy way to quickly prototype a piece of Swift code, to learn the language, or to explore the standard library and Apple’s SDKs — Swift playgrounds have since then become an integral part of many developers’ daily workflows.

However, both Xcode’s implementation of playgrounds — and the dedicated iPad app — are often quite heavily criticized for being unstable, slow and hard to work with. So this week, let’s take a look at some tips and tricks that can make working with Swift playgrounds easier, less error prone, and more productive.

Asynchronous execution

Just like command line tools and scripts, playgrounds are completely synchronous by default. Each line of code is evaluated in an imperative manner, and once the last line has been executed, the program exits.

While this model mostly makes writing code in a playground very simple — we can just start writing in the global scope without the need to create app delegates or runloops or event handlers — it can make working on asynchronous code much harder.

But the good news is that we can actually easily make a playground asynchronous, by either making it display a live view, or — if we’re working on a piece of code that doesn’t have any UI associated with it — by using the needsIndefiniteExecution API, like this:

import PlaygroundSupport

let page = PlaygroundPage.current
page.needsIndefiniteExecution = true

let loader = ImageLoader()
let url = URL(staticString: "https://source.unsplash.com/random")

// Perform our asynchronous operation
loader.loadImage(from: url) { image in
    // Assign the image to a non-variable to have it show up
    // in the playground’s timeline.
    _ = image

    page.finishExecution()
}

Above we’re borrowing the URL(staticString:) initializer from “Constructing URLs in Swift”, which lets us create a non-optional URL using a StaticString.

As you can see above, using the PlaygroundSupport framework — and more specifically the PlaygroundPage API — we’re able to control some aspects of how we want our playground to behave.

By setting needsIndefiniteExecution to true on the current page, we essentially tell the playground to keep running until we tell it to stop — which we do using a call to finishExecution(). It’s usually a good idea to stop execution once we’re done, as to not waste CPU cycles (and precious battery life if we’re working on a laptop) on keeping the playground running.

Handling exceptions

One limitation of both the Xcode and iPad versions of Swift playgrounds is the lack of debugger support. While this normally isn’t a big problem — since instead of using breakpoints to inspect the state of execution, we can get most of the information we need from the playground’s timeline — it can lead to some frustrating situations whenever our code triggers some form of exception.

When working on an app, catching an NSException is quite straightforward, since in Xcode we can set an “Exception breakpoint” that’ll pause our program at the exact spot that caused the exception to be thrown — but when working within a playground we have to get a bit creative, since all we get by default is a SIGABRT crash.

One way to do so is to use the NSSetUncaughtExceptionHandler function to globally assign a closure to be run every time an exception is encountered. That way we can print the exception to help us figure out what happened, like this:

NSSetUncaughtExceptionHandler { exception in
    print("💥 Exception thrown: \(exception)")
}

While not perfect, doing the above does usually give us enough debugging information, since most system-thrown exceptions tend to contain clear descriptions of the errors that caused them.

Decoding bundled files

When prototyping or working on a new feature using a playground, it’s quite common to want to load some form of resource files. For example, we might want to render a UI based on a JSON file containing mocked data, or load a database file that already contains the records that we need.

Just like in an app, we can use the Bundle API to access any resource files that we’ve added to our playground, which lets us do things like extend Decodable to be able to easily create a model instance from a bundled JSON file:

extension Decodable {
    static func decodeFromFile(
        named fileName: String,
        in bundle: Bundle = .main
    ) throws -> Self {
        // Just like in an app, we can use our playground’s
        // bundle to retrieve a URL for a local resource file.
        guard let url = bundle.url(forResource: fileName,
                                   withExtension: "json") else {
            throw MissingFileError()
        }

        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()

        return try decoder.decode(self, from: data)
    }
}

Adding small convenience APIs, like the one above, may not seem like a big deal at first — but can really help increase our productivity when prototyping or tweaking some existing code. With the above in place, all we now have to do to use a decodable model in our playground is to add a JSON file containing its data and call our new API:

let user = try User.decodeFromFile(named: "User")

Since a playground’s code is executed in the global scope, we’re able to simply use try without surrounding the call with do and catch clauses — just like we can in a Swift script or in a command line tool’s main.swift file.

Live rendering

Each playground page comes equipped with a liveView slot that can be used to live-update a piece of UI as our code changes. When a view is assigned to it, Xcode or the Swift Playgrounds app will continuously render that view in its Assistant Editor (and will automatically turn on the needsIndefiniteExecution flag).

But the cool thing is that we can actually render pretty much anything we want using that live view slot — since the type of that property isn’t UIView (as we might assume) — anything conforming to the PlaygroundLiveViewable protocol can be assigned to it.

For example, besides UIView, PlaygroundSupport also makes UIViewController conform to that protocol — making it possible to assign any view controller directly as a playground page’s live view:

let product = try Product.decodeFromFile(named: "Product")
let vc = ProductViewController(product: product)
PlaygroundPage.current.liveView = vc

But perhaps even cooler, is that we can easily make any type we wish to render conform to PlaygroundLiveViewable as well. All we have to do is to convert our type into a PlaygroundLiveViewRepresentation value when asked to do so by the playground — which usually involves wrapping our custom UI code in either a UIView or UIViewController. For example, here’s how we could extend CALayer to be playground live viewable:

extension CALayer: PlaygroundLiveViewable {
    // We create a wrapper view controller that we can
    // assign the layer that we wish to live view to.
    private class LiveViewController: UIViewController {
        var liveLayer: CALayer? {
            didSet {
                oldValue?.removeFromSuperlayer()
                liveLayer.map(view.layer.addSublayer)
            }
        }

        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()

            liveLayer?.position = CGPoint(
                x: view.bounds.midX,
                y: view.bounds.midY
            )
        }
    }

    public var playgroundLiveViewRepresentation: PlaygroundLiveViewRepresentation {
        let vc = LiveViewController()
        vc.liveLayer = self
        return .viewController(vc)
    }
}

With the above in place, we can now easily preview any layer or other Core Animation code that we’re working on, simply by assigning our CALayer as our playground’s live view:

let shape = CAShapeLayer()
shape.frame.size = CGSize(width: 250, height: 250)
shape.path = CGPath(ellipseIn: shape.bounds, transform: nil)
shape.lineWidth = 10
shape.strokeColor = UIColor.red.cgColor
shape.fillColor = UIColor.white.cgColor

PlaygroundPage.current.liveView = shape

One interesting thing to note about the above live view API is that PlaygroundLiveViewRepresentation is implemented as an enum with associated values. That’s noteworthy since that’s a Swift-only feature, it’s not something that can be bridged to or from Objective-C — which works in this case since PlaygroundSupport only needs to support Swift playgrounds, which is also the case for tools like CreateML.

Increasing stability

Finally, let’s take a look at how a Swift playground can potentially be made more stable — to help avoid both crashes and unexpected behavior. While there are no silver bullets (and I know that the Xcode team at Apple are working really hard on continuing to make playgrounds better and more stable), here are 3 things to try out if you’ve experienced problems with playgrounds in the past:

Turning off auto-run

On the Mac, playgrounds are automatically executed whenever the source code was changed. While this is a really cool feature (and when it works, it almost feels like magic), it can be a major source of instability — and all those extra compilation cycles before we’ve finished writing our code can really hurt battery life when working on the go.

To turn off auto-run, simply hold down the mouse button over the little ▶️ button located in the bottom left corner of Xcode’s UI, and select "Manually Run" instead of "Automatically run". Doing so might cost us a bit of convenience, but if we also map ⌘+R to "Run Playground" using System Preferences (Keyboard > Shortcuts > App Shortcuts), running a playground is now as simple (and uses the same muscle memory 😅) as running an app — and tends to be much more stable.

Using frameworks

Like we took a look at in "Writing unit tests in Swift playgrounds", using frameworks can be a great way to get access to our app code within a playground — and doing so can also help improve stability.

Especially if we’re dealing with a larger amount of code, moving the code we’re not actively working on into a framework (and then importing that framework into our playground) means that we can pre-build some of our code using ⌘+B instead of having the playground re-evaluate it on an ongoing basis.

Using a Mac playground

Besides auto-run, another common source of instability in Xcode’s version of playgrounds is caused by the connection between a playground and the iOS simulator. But the good news is that, unless we’re working on iOS-specific code, we can just as well use a Mac playground instead — completely removing the need to involve the iOS simulator and often resulting in a faster and more stable playground.

A playground’s target platform can either be specified when creating it, or changed after the fact using Xcode’s Inspector view. Working with macOS as the target platform for non-UIKit-related code can also be a great way to keep our code as cross-platform compatible as possible, which can really be beneficial later in case we either want to port our whole project to the Mac, or reuse parts of it for a Mac app or stand-alone framework.

Conclusion

I personally love Swift playgrounds. Sure, they’re not perfect, and I do wish they would be faster and more stable (and I’m confident that the Xcode team at Apple will keep improving them with each release) — but the fact that I can quickly fire up a Swift coding environment whenever I want, without having to create a full app project — is such a fantastic way to quickly try out new ideas or to build something small.

In fact, almost all of the sample code on this site was written using Swift Playgrounds — and increasingly more so on the iPad.

What do you think? Do you currently use Swift playgrounds as part of your daily workflows, or is it something you’ll try out? Let me know — along with your questions, comments and feedback — on Twitter @johnsundell.

Happy Holidays, and thanks for reading! 🚀