Blog

Latest Post

Swift Tip: Sharing Code between Backend and Frontend

Over at Swift Talk, we’ve been busy building a new Swift Talk client app in SwiftUI. Because our backend is already written in Swift, we can now share a bunch of code between the client and server.

As a first step, we extracted our Tiny Networking library into its own Github project. It’s a Swift package, and with Xcode 11, it’s now even easier to use across projects.

Then, we went for the low hanging fruit: sharing view helper functions. This gives us a consistent output between our backend and client apps. For example, we share custom formatters to display an episode’s duration, and custom date formatters.

Finally, one of the most interesting cases is sharing the JSON models we use for the API. The backend provides a JSON list of episodes and collections, and we generate that using the Codable infrastructure. To keep things flexible, we have the following setup:

In our backend, our model struct for an episode is called Episode. Rather than directly rendering this to JSON, we created a struct EpisodeView. An EpisodeView is so called because we use it as a (JSON) view on the original data. In our backend, we can then populate it from an episode:

extension EpisodeView {
    init(_ e: Episode) {
        self = EpisodeView(
            id: e.id.rawValue,
            number: e.number,
            title: e.title,
            synopsis: e.synopsis,
            url: Route.episode(e.id, .view(playPosition: nil)).url,
            small_poster_url: e.posterURL(width: 590, height: 270),
            // ...
        )
    }
}

Separating the two types allows us to change the original Episode struct, while maintaining the same API. Likewise, our Episode struct shouldn’t know about URLs for the episode itself, those are added when we create the view.

In our shared code, we also expose a JSON encoder and decoder. By putting those into the shared library, it’s much easier to keep them in sync. For example, we need to ensure that we encode and decode our dates using the same strategy. In our server, we use the shared encoder, and in our client, we use the shared decoder.

In the future, it would be nice to also share a typed specification of routes between the server and the client, in a manner similar to Haskell’s servant library.

To follow our experiments with SwiftUI, subscribe to Swift Talk.

Previous Posts

SwiftUI: Loading Data on Demand

In our previous post, we showed a way to load data from the network in SwiftUI.

The approach we use leaves some outstanding issues. First of all, because view structs can get reconstructed many times over the course of an app (typically, they get constructed much more often than UIViews), the construction of a view should be as efficient as possible. In our example, we reload data from the network every time the ContentView is created. For a root view, this might not be a problem, but in general, it’s a bad idea.

Furthermore, the view might get constructed even when it’s not going to be on screen. For example, a NavigationButton constructs its destination view immediately. This is unlike most UIKit apps, where you typically construct view controllers lazily, just before they get pushed onto the navigation stack.

To delay the loading of the resource until the view is on screen, we experimented with two approaches. In the first approach, we could evaluate the view on demand, by wrapping it in a LazyView wrapper:

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}

This creates the view on-demand when SwiftUI wants to render the body of the lazy view. This can be useful when you work with a NavigationButton.

A second approach is to change the way our Resource works. Instead of loading the data in the Resource‘s initializer, we can load the data once SwiftUI starts subscribing to the object. This is a bit more complicated. We achieve this by using the handleEvents method on our PassthroughSubject. Once somebody subscribes (in practice SwiftUI is the only subscriber), we start loading the data.


final class Resource<A>: BindableObject {    
    // Workaround for initialization
    private(set) var didChange: AnyPublisher<A?, Never> = Publishers.Empty().eraseToAnyPublisher()

    let subject = PassthroughSubject<A?, Never>()
    let endpoint: Endpoint<A>
    var firstSubscriber: Bool = true

    var value: A? {
        didSet {
            DispatchQueue.main.async {
                self.subject.send(self.value)
            }
        }
    }

    init(endpoint: Endpoint<A>) {
        self.endpoint = endpoint
        didChange = subject.handleEvents(receiveSubscription: { [weak self] sub in
            guard let s = self, s.firstSubscriber else { return }
            s.firstSubscriber = false
            s.reload()
        }).eraseToAnyPublisher()
    }

    func reload() {
        URLSession.shared.load(endpoint) { result in
            self.value = try? result.get()
        }
    }
}

Both of the approaches above feel like workarounds, and at the moment of writing, there’s no documentation about how this should be done. Likewise, they both rely on implementation assumptions: the lazy wrapper assumes that the underlying views get displayed once a view’s body is evaluated, and the Resource assumes that a subscriber to didChange means that the view is displayed.

In either case, issues remain. While they both delay the loading of a URL until the view is displayed, neither solves a second problem: every time a view gets recreated, the URL gets loaded again. We’ll try to solve that problem in a future post.

If you’d like to follow our experiments with SwiftUI, we’ll be adding to our Swift Talk Collection over the coming weeks. In the latest series, we use SwiftUI to build a Swift Talk app — the first episode is free to watch.

SwiftUI: Loading Data Asynchronously

In our latest Swift Talk episode, Asynchronous Networking with SwiftUI, we show a technique for loading data on demand using the Tiny Networking library from previous episodes.

We start by creating an endpoint for the network resource we’re interested in. The endpoint combines the URL of the resource with a parse function, which knows how to process the result:

struct User: Codable {
    var name: String
    var location: String?
}

func userInfo(login: String) -> Endpoint<User> {
    return Endpoint(json: .get, url: URL(string: "https://api.github.com/users/\(login)")!)
}

let sample = userInfo(login: "objcio")

To drive SwiftUI updates with the results of network requests, we create a bindable object, Resource, based on an endpoint. A bindable object has a single requirement: it needs to provide a didChange property that SwiftUI will use to observe any changes. The implementation of Resource starts the network request and calls didChange.send once the data is available:

final class Resource<A>: BindableObject {
    let didChange = PassthroughSubject<A?, Never>()
    let endpoint: Endpoint<A>
    var value: A? {
        didSet {
            DispatchQueue.main.async {
                self.didChange.send(self.value)
            }
        }
    }
    init(endpoint: Endpoint<A>) {
        self.endpoint = endpoint
        reload()
    }
    func reload() {
        URLSession.shared.load(endpoint) { result in
            self.value = try? result.get()
        }
    }
}

The Resource class is generic, it can be used with any endpoint. In our example, we’ll use it to load user information from GitHub. The view below displays a loading text by default, but once the data has loaded SwiftUI will re-render the view and display the user’s name and location:

struct ContentView : View {
    @ObjectBinding var user = Resource(endpoint: userInfo(login: "objcio"))
    var body: some View {
        Group {
            if user.value == nil {
                Text("Loading...")
            } else {
                VStack {
                    Text(user.value!.name).bold()
                    Text(user.value!.location ?? "")
                }
            }
        }
    }
}

Unfortunately, the function builder syntax that’s used to write view expressions such as these doesn’t allow if let. Until it does, we have to write an explicit nil check and force-unwrap in the else branch.

SwiftUI is very new, and we still have much to learn. We’ll be exploring the framework more in the coming weeks, and you can learn with us on Swift Talk — the first episode is public. 🙂

Advanced Swift Fourth Edition, with Videos!

Advanced swift hero 1500w

Advanced Swift

The fourth edition of Advanced Swift is out!

Fully updated for Swift 5, the book has been rewritten and extended, including a new chapter on Enums, and a video companion series as an optional upgrade. As with the previous edition, the full text is available as a Swift Playground.

We have completely rewritten the chapters on Structs and Classes, Generics, and Protocols. New content and revisions have been added to the chapters on Strings, Collection Protocols, and Error Handling. The chapter order has also been improved: Collection Protocols now comes later, to smooth out the learning curve.

We also welcome Florian Kugler as a co-author, joining Ole Begemann, Ben Cohen, and Chris Eidhof.

With Video

We’re bringing the video bundle pioneered by App Architecture to Advanced Swift.

Where the book excels as a medium for conceptual discussion, the videos approach the subject from a different angle: they show how those techniques apply to practical problems.

In five separate episodes, we demonstrate C Interoperability, String Parsing, Collection Protocols, Functions, and Encoding & Decoding Graphs. Using live-coding, pair programming, and a conversational style, the videos invite you to think through the concepts with us, as we apply them.

For a more in-depth look, see our full table of contents.

Who Is The Book For?

For those new to Advanced Swift, the book ranges from low-level programming, such as string internals and wrapping a C library, to high-level abstractions, such as programming with generics and protocols. It intends to answer many of the “How do I do this?” or “Why does Swift behave like that?” questions that we often see asked.

If you’re an experienced (though not necessarily expert) programmer, such as an existing Apple-platform developer, this book is for you. It’s also for those coming from other languages such as Java or C++ who want to bring their knowledge of Swift to the same level as their “go-to” language. It’s also suitable for new programmers who have already started on Swift, have grown familiar with the basics, and are looking to take things to the next level.

In short, if you’re reading Apple’s The Swift Programming Language and want to dig deeper, this book is for you.

Availability

The book and videos are available today, directly from our site. The Ebook includes PDF, ePub, and mobi versions. If you prefer a physical copy, we have a paperback edition. All versions come with full sample code.

If you already own an Ebook edition of Advanced Swift, this is a free update.

The videos are exclusive to the Ebook + Videos edition, and can be purchased as a separate upgrade if you already own the book.

All our books are available as bundles: see the complete collection.

Sales and checkout are managed by Gumroad. If you need assistance please contact their help team. Sales for the physical copy are fulfilled by Amazon.

 

Best from Berlin

Chris, Ole, and Florian

 

Swift Tip: Wrapper Functions

For this year’s update to Advanced Swift, we’re adding the option for a video bundle (just like we did for App Architecture). One of the videos in the bundle shows how we wrap Cairo, a library for drawing vector graphics.

In our wrapper, we need to do the following:

let surface = try SVGSurface(size: CGSize(width: 800, height: 600))
let context = try Context(surface)
context.drawSomething()
surface.finish()
print(surface.data)

Before we can access the data of the context, we should call surface.finish(). And after we call surface.finish(), we really shouldn’t be drawing using context anymore. Instead of putting this into the documentation, we can also provide a different API that prevents us from making this mistake:

func svg(size: CGSize, _ draw: (Context) -> ()) throws -> Data {
    let surface = try SVGSurface(size: size)
    let context = try Context(surface)
    draw(context)
    surface.finish()
    return surface.data
}

Now when we want to create an SVG, we call the function with our drawing code:

let data = try svg(CGSize(width: 800, height: 600)) { context in
    context.drawSomething()
}

In the sample above, we can’t access the surface directly, and we can’t forget to call finish() – this is taken care of by the wrapper.

We can use the same technique in many places. For example, in Core Graphics (which happens to be similar to Cairo), there’s an API that saves the graphic state of a CGContext, and another one that restores it. Calls to these APIs should be balanced. Rather than calling them directly, again we can write a wrapper function that balances the calls for us:

extension CGContext {
    func withLocalGraphicsState(_ draw: () -> ()) {
        saveGState()
        draw()
        restoreGState()
    }
}

You can use similar APIs whenever you’re dealing with resources that need balanced calls: opening and closing a file, locking and unlocking a shared resource, or pushing and popping some state. We use this pattern throughout our projects. It’s much nicer to move these possible points of failure out of our domain specific code, and into helper code that we can test and verify once.

The video bundle will be released shortly, alongside the new edition of Advanced Swift. If you already own the Ebook, the edition update will be free. The videos will be optional, as a paid upgrade. Watch our Twitter for an announcement!