Programmatic navigation in SwiftUI with NavigationPath and navigationDestination

One of the key features that was missing from SwiftUI when it first shipped was a good way to do programmatic navigation. There were some ways to handle this before iOS 16 introduced NavigationPath but it wasn’t very satisfying to use those APIs and they could be rather unreliable at times. To see an example, take a look at this post I wrote about handling deeplinks on iOS 14.

In this post, I’d like to revisit programmatic navigation through iOS 16’s NavigationPath API which is a huge leap forward in terms of developer experience and reliability at the same time.

In this post we’ll look at:

  • Understanding iOS 16’s navigation API
  • Navigating through a NavigationPath

Understanding iOS 16’s navigation API

On iOS 16, Apple introduced the NavigationStack view. This is a view that’s pretty much analogous to UIKit’s UINavigationController and it allows developers to implement stack-based navigation. This is the kind of navigation that you’ll actually find in most apps that allow you to navigate into items that are shown in a list for example.

A navigation view in iOS has a stack of views that it holds on to as a hierarchy of how the user got to where they currently are. For example, the root view might be a list, the next view might be a movie view and the next one might be a view where users can view the cast of a movie. Each view would exist on the stack and a user can navigate back one level by swiping from the edge of their screen.

I’m sure you’re familiar with the UX of this.

The stack of views that represents the navigation hierarchy wasn’t available to use until iOS 16. The main difference between UIKit’s UINavigationController and how NavigationStack manages its navigation is that in SwiftUI we can actually navigate based on models.

This means that we can map instances of, for example, a Movie model to a MovieView that can present a movie to the user.

Essentially this means that we can model a navigation hierarchy using model data rather than views.

Let’s take a look at an example of how we can set up a NavigationStack along with a detail page for a given model type. We won’t introduce a NavigationPath just yet. Behind the scenes our NavigationStack will manage its own path if we don’t provide one so we’ll just rely on that for now.

The code below defines a simple list view with NavigationLink views to enable navigation. Notice that the NavigationLink receives a value instead of a destination. Also, notice how we’re applying a navigationDestination view modifier to specify a destination view for our model.

struct ContentView: View {
  @State private var exercises: [Exercise] = Exercise.sample

  var body: some View {
    NavigationStack {
      ExercisesList(exercises: exercises)
        .navigationDestination(for: Exercise.self, destination: { exercise in
          ExerciseDetail(exercise: exercise)
        })
    }
  }
}

struct ExercisesList: View {
  let exercises: [Exercise]

  var body: some View {
    List(exercises) { exercise in
      NavigationLink(value: exercise, label: {
        ExerciseListItem(exercise: exercise)
      })
    }
    .navigationTitle("My exercises")
  }
}

What’s especially interesting here is where we apply the navigationDestination view modifier.

I chose to add it to my list. This means that any NavigationLink inside of my list with an instance of Exercise as its value will use the destination view that I provided as its view. This means that I can define my destination views all in one place which means that I can quickly reason about which view will be shown for a model.

If I were to define a second navigationDestination for the same model type on my List, that second destination would overwrite my first. This allows me to override the destination if needed so that each view can still explicitly define its own “exit views” but it’s not required. This is really powerful and allows for very flexible navigation setups.

At this point, we’re able to push new models onto our navigation stack’s navigation path using our navigation link and we’ve configured a destination view using the navigationDestination view modifier.

Now let’s set up a navigation path so we can start performing some programmatic navigation, shall we?

Navigating with a NavigationPath

A NavigationStack can be set up with a NavigationPath object which will allow you to gain control over the stack’s navigation hierarchy.

The simplest way to set up a NavigationPath is as follows:

struct ContentView: View {
  @State private var exercises: [Exercise] = Exercise.sample
  @State private var path = NavigationPath()

  var body: some View {
    NavigationStack(path: $path) {
      ExercisesList(exercises: exercises)
        .navigationDestination(for: Exercise.self, destination: { exercise in
          ExerciseDetail(exercise: exercise)
        })
    }
  }
}

With this code, we’re not yet doing anything to gain control of our navigation path. We’re just making an instance of NavigationPath and we pass a binding to NavigationStack. From now on, whenever we navigate to a new view, the model that’s used as a value will be added to the path we created.

Essentially, when a user taps on a NavigationLink, we take the model instance that was passed as a value and it’s added to the navigation path automatically.

We can pass any Hashable model as the value for a navigation destination and we can also mix models. So we could pass instances of Exercise, Int, String, and more to the same navigation path.

In fact, you normally don’t worry about which model types you pass. You just pass the model that you need to draw your destination view and you let the system handle everything else.

Let’s take a look at how we can replace our NavigationLink with a Button so we can manually append our model to the NavigationPath that we’ve created before.

We can create a binding to the NavigationPath and we pass it to the ExercisesList, allowing it to append new items to the path which will allow the NavigationStack to navigate to the destination for our model:

struct ContentView: View {
  @State private var exercises: [Exercise] = Exercise.sample
  @State private var path = NavigationPath()

  var body: some View {
    NavigationStack(path: $path) {
      // 1
      ExercisesList(exercises: exercises, path: $path)
        .navigationDestination(for: Exercise.self, destination: { exercise in
          ExerciseDetail(exercise: exercise)
        })
    }
  }
}

struct ExercisesList: View {
  let exercises: [Exercise]
  // 2
  @Binding var path: NavigationPath

  var body: some View {
    List(exercises) { exercise in
      Button(action: {
        // 3
        path.append(exercise)
      }, label: {
        ExerciseListItem(exercise: exercise)
      })
    }
    .navigationTitle("My exercises")
  }
}

Before I explain the code, let me say that I don’t think this is a good idea. The code was better with NavigationLink. That said, the point of this example is to demo putting items in a NavigationPath programmatically which we can do from a button handler.

First, we pass a binding to our navigation path to the list view. This means that now our NavigationStack and ExercisesList both have access to the exact same NavigationPath instance.

The ExercisesList was updated to take a binding to a NavigationPath, and we’ve swapped the NavigationLink out in favor of a Button. In the button handler, I call append with the Exercise model for the button on path. This will add the model to the path which will cause SwiftUI to navigate to the destination view for that model.

This is really cool!

In addition to appending elements to the path, we can actually remove items from the path too by calling remove on it.

We can even get the number of items on the path to implement a “pop to root” style function:

func popToRoot() {
  path.removeLast(path.count)
}

This function will remove all elements from the navigation stack’s path, only leaving its root to be displayed.

The API for NavigationPath is really flexible. You can even add multiple views in a single pass, resulting in the last added view becoming the top one and all others being part of the stack so the user sees them when they navigate back.

In Summary

With NavigationPath we’ve gained loads of power in terms of being able to navigate programmatically. By leveraging model-based navigation we can represent a navigation stack’s hierarchy as data rather than views, and we’re able to pass our NavigationPath around through bindings in order to allow views to append new models to the path.

Handling deeplinks and restoring navigation stacks with NavigationPath is loads better than it used to be pre iOS 16 and I’m sure that Apple will keep improving NavigationPath over time to make managing navigation through code better and better.

Turn off sidebar hiding on NavigationSplitView in SwiftUI

By default, a NavigationSplitView in SwiftUI will show users an option to toggle the visibility of the sidebar. If you want to prevent this button from showing up so that users will always have to see your sidebar, you can do this by applying the toolbar(removing:) view modifier to your split view's sidebar as follows:

NavigationSplitView(sidebar: {
  ExercisesList()
    .toolbar(removing: .sidebarToggle)
}, detail: {
  ExerciseDetail(exercise: exercises.first!)
})

The downside of doing this is that the button is hidden both in portrait and landscape modes. The result is that landscape users can no longer access your app's sidebar.

To fix this you can try applying the view modifier conditionally based on the device's orientation but that's not ideal; apps in landscape might also show the split view as a single column. I have yet to find a good, reliable solution to conditionally presenting and hiding the sidebar toggle.

One upside for users is that they can still summon the sidebar in portrait mode by swiping from the leading edge of the screen towards the middle. It's not perfect, but it's better than nothing I suppose.

How to decide between a Set and Array in Swift?

Collections are a key component in any programming language. We often refer to collections as Array or Set but there are several other kinds of collections in programming like String (often a collection of type Character) and ArraySlice (referring to a part of an array).

In this post, I’d like to explore two of the most common collection types; Set and Array. We’ll take a look at the key characteristics for each and we’ll explore use cases where we can use each.

We’ll cover the following topics:

  • Understanding Array’s key characteristics
  • Understanding Set’s key characteristics
  • Exploring performance considerations
  • Use cases for Set and Array

Understanding Array’s key characteristics

An Array in Swift is defined as follows:

let myList = ["one", "two", "three"]

If we fully write out the type for myList, we’d write let myList: Array<String>. That’s because arrays in Swift can only contain a homogeneous collection of objects. In other words, it can only contain objects of a single type. In this case that type is String.

We can have any kind of object in an Array, the only restriction is that your array must only contain objects that are all of the same type. In other words, we can’t have an array that contains both Int and String, but we can have an array that contains a custom enum:

enum MixedValue {
  case int(Int)
  case string(String)
}

let myList: [MixedValue] = [.int(1337), .string("Hello")]

Our array in this example only contains values of type MixedValue. Even though the associated values for my array are mixed, Swift will allow this because our array is still an array of MixedValue.

Items in an array are ordered. This means that items in an array will always be in the same order, no matter how many times you iterate over your array. For example, if you use a for loop to iterate your array thousands of times, the ordering of your elements won’t change.

You can reorder your array if you’d like by sorting it, and from that point on the new sorting will remain as the single ordering for your array.

Arrays can also contain duplicate values. This means that you can have multiple objects that are equal in the same array.

If we want to find an item in an array we can use the first(where:) function to iterate the array until we find what we’re looking for:

let myList: [Int] = [1337, 1338, 1339]

let item = myLIst.first(where: { $0 == 1340 })

The code above would iterate all items, not find a match based on my comparison and set item to nil.

There’s a lot more to know about working with arrays and collections in general, but to keep this post focused on the comparison between set and array, these are the key characteristics that I wanted to show you on array.

Arrays are meant to hold data that is ordered and this data doesn’t have to be unique

Understanding Set’s key characteristics

A Set in Swift holds a single type of object, just like Array does. For example, we can have a Set of strings like this:

let mySet: Set<String> = ["hello", "world"]

Notice how defining the set looked pretty much the same as defining an array which would have looked as follows in this specific case:

let myArray: Array<String> = ["hello", "world"]

Both sets and arrays can be initialized using array literal syntax.

One key difference between sets and arrays is that elements in a Set must be Hashable, and a Set only contains unique values.

This means that we can add items like String to a Set because String is Hashable. We can also add custom types to a Set as long as the type is Hashable.

Also note that I wrote earlier that items in a Set must be unique. Items in a Set are compared based on their hash value and when you add a second item with a hash value that’s already in your set the old item is removed and the new one is kept in the set instead.

If we want to find out whether an item in our Set exists we can use contains and pass the value we’re looking for:

let mySet: Set<String> = ["hello", "world"]
let hasValue = mySet.contains("hello")

If we want to find a specific item in our Set we can use the same first(where:) method that you saw earlier on Array. That’s because this method is part of the Collection protocol that both Array and Set conform to.

When you iterate over a set, the order of elements in the set is not guaranteed. This means that when you perform many iterations, you’ll notice that sometimes the order of items in your set gets shuffled. That’s expected.

A Set is meant to hold on to unique, unordered data that conforms to Hashable

If you require Set semantics but also need ordering, you could consider pulling in the swift-collections package and use its OrderedSet object which holds unique Hashable items but it also maintains an ordering. In a way, OrderedSet is an Array that enforces unique items and has O(1) lookup. Kind of the best of both worlds.

Performance considerations

It’s hard to give you a complete overview and advice for performance comparisons between Set and Array because there’s loads of things we can do with them.

The key aspect of performance that we can reason about is looking up items in either.

An array performs an item lookup in O(n) time. This means that in a worst case scenario we’ll need to look at every element in our array before we find our item. A Set on the other hand performs a lookup in O(1). This means that a set always takes the exact same amount of time to find the item you want to look for. This is orders of magnitude better than O(n), especially when you’re dealing with large data sets.

In Summary

In the end, the decision between Set and Array is one that I believe is made best based on semantics. Do you have a list of Hashable items that need to be unique in a collection without ordering; you’re thinking of a Set. Do you care about order? Or maybe you can’t make the items Hashable, then you’re probably thinking of an array.

There is of course the exception where you might want to have unique items that are Hashable while maintaining order, in which case you can choose to use an OrderedSet from swift-collections.

I would always base my decision on the above and not on things like performance unless I’m working on a performance-critical piece of code where I can measure a difference in performance between Set and Array.

Swift’s “if” and “switch” expressions explained

In Swift, we sometimes want to assign a property based on whether a certain condition is true or false, or maybe based on the value of an enum. To do this, we can either make a variable with a default value that we change after checking our condition or we define a let without a value so we can assign a value based on our conditions.

Alternatively, you might have used a ternary expression for simple assignments based on a conditional check.

Here’s what a ternary looks like:

let displayName = object.isManaged ? object.managedName : object.name

This code isn’t easy to read.

Here’s what it looks like if I had written the exact same logic using an if statement instead.

let displayName: String

if object.isManaged {
  displayName = object.managedName
} else {
  displayName = object.name
}

This code is much easier to read but it’s kind of weird that we have to declare our let without a value and then assign our value afterwards.

Enter Swift 5.9’s if and switch expressions

Starting in Swift 5.9 we have access to a new approach to writing the code above. We can have switch and if statements in our code that directly assign to a property. Before we dig deeper into the rules and limitations of this, let’s see how an if expression is used to refactor the code you just saw:

let displayName = if object.isManaged {
  object.managedName
} else {
  object.name
}

This code combines the best of both worlds. We have a concise and clear approach to assigning a value to our object. But we also got rid of some of the unneeded extra code which means that this is easier to read.

We can also use this syntax with switch statements:

let title = switch content.type {
  case .movie: object.movieTitle
  case .series: "S\(object.season) E\(object.episode)"
}

This is really powerful! It does have some limitations though.

When we’re using an if expression, we must provide an else too; not doing that results in a compiler error.

At the time of writing, we can only have a single line of code in our expressions. So we can’t have a multi-line if body for example. There is discussion about this on the Swift Forums so I’m sure we’ll be able to have multi-line expressions eventually but for the time being we’ll need to make sure our expressions are one liners.

What are enums in Swift?

Swift comes with types of objects that we can use to write type declarations. They all have their own distinct features, upsides, and downsides. In this post I’d like to zoom in on the enum type so you can get a sense of what enums are, and when they can be useful.

In this post we’ll cover the following topics:

  • Understanding the basics of enums
  • Knowing when an enum should be used
  • Avoiding enum overuse

Let's jump right in!

Understanding the basics of enums

In Swift, enums are values types that are declared using the enum keyword. Every possible value of the enum is called a "case". Enums can either be used as a list of cases that models specific (mutually exclusive) values or constants. For example, we could represent a list of operating systems for SwiftUI apps as follows:

enum OperatingSystem {
  case iOS, iPadOS, macOS, visionOS, tvOS, watchOS
}

If we were to pass an operating system to a function or assign it to a variable, that would look a little like this:

// assigning a variable
var os: OperatingSystem = .macOS

// or passing to a function
let binary = buildAppBinaryFor(.watchOS)

Since enums are first class citizens in Swift, they can conform to protocols, define functions, and even have computed properties. Note that you can’t add stored properties to an enum like you can to a struct.

Enums are incredibly useful when we need to represent a finite set of predefined states. With a enum, it’s impossible to create or use state that are not correct which can be incredibly useful. Imagine that you’d have to manually type out an animation type as a string in SwiftUI instead of passing .easeInOut and knowing that if your code compiles you passed a correct animation type. If you had to pass the string "easeInOut" the compiler wouldn’t be able to type check that you passed a valid value and you could be bitten by typos such as "aeseInOut" which isn’t ideal.

Even though you can’t leverage stored properties when using enums, you can associate values with enums and you can give them raw values.

An enum with raw values looks like this:

enum OperatingSystem: String {
  case iOS = "iOS"
  case iPadOS = "iPadOS"
  case macOS = "macOS"
  // etc...
}

In this case, my raw enum values match the enum case names directly. This means that instead of adding every possible value after the case name, I can specify that my enum uses a raw String value without modifying my cases:

enum OperatingSystem: String {
  case iOS, iPadOS, macOS, visionOS, tvOS, watchOS
}

Defining enums with raw values is incredibly useful when you want to create your enum values from external resources like when you’re decoding JSON. Having raw values allows you to create enum instances like this:

let os: OperatingSystem? = OperatingSystem(rawValue: "iOS")

Of course, when hardcoding the raw value it would be much better to just use .iOS directly but if the raw value comes from an external source we can try to make an enum with the value we received. If this works, we get an enum case back and if not, we get nil. This happens when we pass a raw value that’s not defined on the enum.

The last thing I’d like to show you before we talk about up and downside of enums is defining an enum with an associated value.

An example of such an enum is Swift’s built-in Result type:

enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

This enum uses generics to allow developers to use custom values for Success and Failure but you can also define an enum without generics for associated values. For example:

enum Animation {
  case none
  case easeIn(duration: CGFloat)
  // ...
}

With this Animation enum we can pass different animation configurations that are all grouped as possible values under a single type. This is super convenient!

If you get to a point where you’d like to compare some value to an enum case that has an associated value, you’ll want to read up on if case let syntax using this post I have on that topic.

Now that you know a bit about what enums are, let’s discuss when it makes sense to use them.

Knowing When to Use an Enum

Enums are ideal for managing a set group of related values in a way that makes your code clearer and safer. Learning to recognize when an enum would make sense is a key skill that will truly help you to write better and safer code over time.

The list below can be used to help you figure out whether an enum fits your current use case:

  • Enums are perfect when a variable can only take a limited set of predetermined values. If you're dealing with options, states, or modes within your application that are finite and known, enums help enforce this constraint with the help of the compiler.
  • Enums are excellent for managing state in your applications. For instance, if you have a UI control with multiple states like active, inactive, disabled, etc., an enum ensures you handle all possible states while also preventing developers from passing impossible or incorrect states.
  • If you’re using constants that are closely related and should be grouped under a single type, enums make this organization clear and logical. For example, error types, configuration options, and other relatively short finite lists of values.

Over time you’ll grow more and more confident in your ability to know whether an enum makes sense for your use case. I think it’s important to understand that not everything can or should be represented as an enum.

For example, I think it’s wise to make sure that your enums are fairly small. An enum that holds dozens of cases is probably too large and you’re no longer grouping closely related values together. It could make sense to refactor your large list of cases into a smaller list that uses associated values to group sub-values together.

It’s also a good idea to keep a close eye on how Apple uses enums in Swift and SwiftUI themselves. Using enums for similar scenarios should give you some confidence that you’re choosing the right tool for the job.

And when in doubt, refer to the list above and trust your instincts; they might be better than you think.

How to add a privacy manifest file to your app for required reason API usage?

Apple has recently introduced a new requirement that makes it so that apps that use certain APIs for Apple's mobile platforms (iOS, iPadOS, tvOS, watchOS) must declare their intended use of certain APIs. This requirement has gone in effect on May 1st which means that any app updates or submissions that don't meet Apple's new requirements will be rejected with a "Missing API Declaration" message also referenced as ITMS-91053.

In this post, I'd like to show you how you can add a privacy manifest file to your app so that you can resolve rejections related to ITMS-91053.

We'll go over the following topics:

  • Which APIs require a declaration in a privacy manifest?
  • How do I add a privacy manifest to my project?
  • What about third party SDKs and app extensions?

Let's dive right in, shall we?

The easiest way for you to build your privacy manifest is through my Privacy Manifest Generator. Use it alongside this article to effortlessly get your manifest in order.

If you prefer to learn from videos, watch the video below:

Which APIs require a declaration in a privacy manifest?

Starting May 1st 2024, there's a large list of APIs that require a declaration in your privacy manifest file. For a full overview, you should take a look at Apple's documentation since the list is simply too long for me to repeat here.

Generally speaking though, if you use an API from one of the following categories you almost certainly will need to add a declaration to your privacy manifest:

  • File timestamp APIs
  • System boot time APIs
  • Disk space APIs
  • Active keyboard APIs
  • User Defaults APIs

Based on this list, I think it's highly likely that you'll be adding a privacy manifest to your app even if you're running a small and simple app because a lot of apps use UserDefaults to store some basic user information.

For the purposes of this post, I'll show you how you can add an appropriate declaration to your privacy manifest for UserDefaults. The steps are pretty much the same for every API, they just have different keys that you're supposed to use.

How do I add a privacy manifest file to my project

You can add your privacy manifest just like you add any other file to your project. In Xcode, select file->new->file and look for the privacy manifest file type:

Searching for Privacy in the new file picker

With the privacy manifest file type selected, click next to add your privacy manifest to your app target. It doesn't select your target by default so don't skip doing this yourself. You should keep the default name that Xcode chose for you; PrivacyInfo.

Adding the privacy manifest to your target

Now that you have your privacy manifest added to your app, it's time to add the correct contents to it.

The first step in the process of figuring out which keys you need to add is to go to Apple's requirements page to find the reference codes that best apply to your usage of required reason APIs:

A screenshot of Apple's documentation for UserDefaults required reasons.

In the case of UserDefaults, we're probably interested in one of two keys: CA92.1 or 1C8F.1 depending on whether you're building an app that uses an App Group. Make sure you read every description carefully to ensure you're not missing any small details or nuances in the descriptions; they can be prety hard to read sometimes.

My app isn't part of an App Group so I'll need to declare CA92.1 in my privacy manifest file.

First, I'll need to add the Privacy Accessed API Types key to my privacy manifest file. Do this by clicking the little + icon next to the App Privacy Configuration key that's already in the privacy manifest. The type of Privacy Accessed API Types should be an array.

Next, add a new item to your Privacy Accessed API Types array. The type of this new item should be a dictionary and in the case of accessing UserDefaults, you'll need to add a key of Privacy Accessed API Type with the valueNSPrivacyAccessedAPICategoryUserDefaults to this dictionary first. The second key you add to your dictionary is Privacy Accessed API Reasons which is an array. To that array, you add the code for your access reason. In our case that's CA92.1.

It's pretty tough to correctly describe this plist so let's just go ahead and look at an example:

A screenshot of the privacy manifest file

Note that I only pasted CA92.1 into my privacy manifest as my value for the access reason and Xcode expanded that into the text you see in the screenshot. I personally find it easier to look at the raw XML for this file so if you right click it and select open as you can view the XML source code:

Example of opening a privacy manifest as source code

Here's what the source code for my privacy manifest looks like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSPrivacyAccessedAPITypes</key>
    <array>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>CA92.1</string>
            </array>
        </dict>
    </array>
</dict>
</plist>

Repeat the steps above for any other required reason APIs you access. If you forget any, Apple will email you with information on which keys you forgot in your rejection email which will help you figure out what to add if you're missing anything.

What about third party SDKs and app extensions?

Every binary in your app must declare its own privacy manifest. So if you have app extensions they must declare required API usage seperately; you can't put all declarations in a single file in your app target.

Your app extensions can use the same files, but they still need to have the file added to their targets explicitly to have them correctly be discoverd.

Third party SDKs must also include a privacy manifest explicitly so if you work on an SDK, now is the time to really make sure you have this done.

In Summary

Adding a privacy manifest file is a new requirement from Apple that, in my opinion, could have been handled better. Manually working on plist files is a tedious job and the keys and values aren't that easy to manage.

Hopefully this post will help you get your app covered for this new requirement so that you can avoid nasty surprises when you submit your app for review!

What is defer in Swift?

Sometimes, we write code that needs set some state or perform some work at the start of a function and at the end of that same function we might have to reset that state, or perform some cleanup regardless of why we’re exiting that function.

For example, you might have a function that creates a new Core Data object and depending on whether you’re able to enrich the object with data from the network you want to exit the function early. Regardless of how and why you exit the function, you want to save your newly created object.

Writing our code without defer

Here’s what that code would look like without Swift’s defer statement

func createMovie(
  named title: String,
  in context: NSManagedObjectContext
) async throws -> Movie {

  let movie = Movie(context: context)
  movie.title = title

  guard let data = try? await network.fetchExtraMovieData() else {
    try context.save()
    return movie
  }

  movie.rating = data.rating

  try context.save()
  return movie
}

Let me start by saying that there are other ways to write this code; I know. The point isn’t that we could refactor this code to have a single return statement. The point is that we have multiple exit points for our function, and we have to remember to call try context.save() on every path.

Cleaning up our code with defer

With Swift’s defer we can clean our code up by a lot. The code that we write in our defer block will be run whenever we’re about to leave our function. This means that we can put our try context.save() code in the defer block to make sure that we always save before we return, no matter why we return:

func createMovie(
  named title: String,
  in context: NSManagedObjectContext
) async -> Movie {

  let movie = Movie(context: context)
  movie.title = title

  defer {
    do {
      try context.save()
    } catch {
      context.rollback()
    }
  }

  guard let data = try? await network.fetchExtraMovieData() else {
    return movie
  }

  movie.rating = data.rating

  return movie
}

Notice that we changed more that just dropping a defer in our code. We had to handle errors too. That’s because a defer block isn’t allowed to throw errors. After all, we could be leaving a function because an error was throw; in that case we can’t throw another error.

Where can we use a defer block?

Defer blocks can be used in functions, if statements, closures, for loops, and any other place where you have a “scope” of execution. Usually you can recognize these scopes by their { and } characters.

If you add a defer to an if statement, your defer will run before leaving the if block.

Defer and async / await

Defer blocks in Swift run synchronously. This means that even when you defer in an async function, you won’t be able to await anything in that defer. In other words, a defer can’t be used as an asynchronous scope. If you find yourself in need of running async work inside of a defer you’ll have to launch an unstructured Task for that work.

While that would allow you to run async work in your defer, I wouldn’t recommend doing that. Your defer will complete before your task completes (because the defer won’t wait for your Task to end) which could be unexpected.

In Summary

Swift’s defer blocks are incredibly useful to wrap up work that needs to be done when you exit a function no matter why you might exit the function. Especially when there are multiple exit paths for your function.

Defer is also useful when you want to make sure that you keep your “start” and “finish” code for some work in a function close together. For example, if you want to log that a function has started and ended you could write this code on two consecutive lines with the “end” work wrapped in defer.

In my experience this is not a language feature that you’ll use a lot. That said, it’s a very useful feature that’s worth knowing about.

Deciding between a computed property and a function in Swift

In Swift, we can use computed properties to derive a value from other values defined on the same object. Being able to do this is super convenient because it means that we don’t have to manually make sure that we update derived properties every time one of the “source” values changed. We’ll just recompute the property every time it’s accessed!

If you prefer to learn from video, here's the companion video for this blog post:

This is very similar to having a function that takes no arguments and returns a value:

struct User {
  let givenName: String
  let familyName: String

  // Should we use this?
  var fullName: String {
    return "\(givenName) \(familyName)"
  }

  // Or this?
  func fullName() -> String {
    return "\(givenName) \(familyName)"
  }
}

So how do we make a choice between a function with no arguments and a computed property?

I like to keep the following rules of thumb in mind:

  • Accessing a property should never have side effects; if accessing the property mutates any values on your object, you should use a function.
  • Accessing a property should (ideally) have O(1) complexity (learn more about Big-O and what O(1) means right here. For functions it's more expected that they might be O(n) or worse.
  • Your property’s computation should be “simple”. This is probably the most subjective of all but if you’re writing more than a handful of lines you should ask yourself whether a function would look better.
  • The property’s output should be deterministic. In other words, accessing the same property multiple times in a row should get me the same result every time. If not, use a function; it fits the non deterministic behavior better in my opinion.

When I apply these rules to the example above, I would pick a computed property for this one. We can compute the name in constant time, the property's getter would be simple (one line), and the output is completely free of any side-effects. A perfect candidate for a computed property.

Of course, these are all just my opinions but I’ve found that most developers that I’ve worked with over the years either agree with these rules or have rules that are only slightly different from mine.

How do you decide between a function or a computed var? Let me know on Mastodon or Twitter!

if case let in Swift explained

In Swift, we can use the case keyword in multiple places. Most commonly, a case is used in switched but since you’re here, you might have seen a case in combination with an if statement.

In this post, we’ll explore different places where we can use the case keyword to perform something called pattern matching in Swift.

Pattern matching is a powerful feature of Swift that allows us to perform highly elegant checks to see if a given type matches a certain value.

Understanding pattern matching

The syntax for if case let is somewhat complex. So let’s start with a quick code sample that demonstrates how you can write an if statement that attempts to match an enum case:

enum ShapeType {
  case rectangle, triangle, circle
}

let myShape = ShapeType.rectangle

if case .rectangle = myShape {
  print("myShape is a rectangle")
}

Now, let me start by saying we didn’t need to use the case syntax here. We could have just as well written the following:

if myShape == .rectangle {
  print("myShape is a rectangle")
}

However, I like the earlier example because it introduces the case syntax in a pretty clean way.

Now, before I dig in to show you the case let syntax I’d like to take a look at the form of pattern matching in Swift that’s most likely the one you’re most familiar with:

switch myShape {
case .rectangle:
  print("myShape is a rectangle")
case .triangle:
  break
case .circle:
  break
}

A switch in programming allows us to write a list of patterns that we want to compare a given value to. This is much more convenient that writing a bunch of if / else statements.

The case keyword in Swift does not perform any special magic. Instead, it invokes a special operator that compares our pattern (whatever we write after case) to a value (the value we’re switching over in a switch).

So… how does that help you understand if case let syntax?

Understanding if case let

Once you know that if case .rectangle = myShape invokes a comparison between .rectangle and myShape the following suddenly makes a little more sense:

enum LoadingState {
  case inProgress(Task<String, Never>)
  case loaded(String)
}

let state = LoadingState.loaded("Hello, world")

if case .loaded(let string) = state {
  print("Loaded string is \(string)")
}

// or

if case let .loaded(string) = state {
  print("Loaded string is \(string)")
}

In both comparisons, we compare our enum case of .loaded and we assign its associated value to a constant. I prefer case .loaded(let string) myself because it looks a little less strange that case let .loaded(string) but they’re functionally equivalent.

And in a switch, you’d use the same patterns to match against which always helps me to remember:

switch state {
case .inProgress(let task):
  break
case .loaded(let string):
  print("Loaded string is \(string)")
}

Again, the pattern here is that we compare our case to a value. In a switch this looks a lot more natural than it does in an if statement but they’re the same under the hood and they both use the underlying ~= comparator.

That said, writing if case .loaded(let string) = state when you’re only interested in a single case is certainly more convenient than writing a full blown switch when you’re only interested in a single case.

How Enterprise level CI/CD with AppCircle helps you scale

As teams grow and companies mature you’ll often find that it gets harder and harder to manage processes that seemed to be so simple before.

When I worked in startups one of my favorite things was how quick the feedback cycle was on pretty much everything I did. When someone designed a new feature we could build that and ship it on Testflight as quick as a couple of hours. If the designer liked the way the implemented feature works they would sign off and off to App Review we’d go.

Usually everybody in the company would be on the Testflight version of an app and they’d install it whenever they wanted. It was all just a formality anyway because in a startup it’s important to keep shipping and improving. Feedback from other departments is great but at the end of the day you’re aiming to ship new features and improvements on a regular cycle.

In small teams you can manage these cycles quite easily. You probably don’t need any automation and you definitely don’t need advanced features and testing strategies that help you get multiple alpha and beta versions of your app into different teams’ hands.

In this post, I’d like to look past the startup phase and fast forward into the problems that arise once you reach a point where you could be considered an enterprise company. These are usually companies with large dev teams, multiple departments, and heightened security needs because of the amount of data and information they process.

There are three aspects of infrastructure in an enterprise environment that I’d like to highlight:

  • Shipping different builds in parallel
  • The importance of security and data ownership
  • Automating builds and app delivery

This is a sponsored post for AppCircle. Note that every sponsored post on this website is an honest review of a product and is always an accurate representation of my thoughts and opinions. Sponsored posts help keep the content on this site available for free

Shipping different builds in parallel

As projects grow more complex it’s not uncommon to want to have multiple versions of your app installed on a testing device. For example, you might be working on a new feature that relies on your server’s staging environment while also applying some bug fixes on your app’s production build. And maybe alongside these two builds you also want to have the App Store version of your app installed.

It might sound like a lot, overkill even, but being able to use a couple of different bundle identifiers for your apps to install them alongside each other is incredibly useful even when you’re just a small team.

In a larger company you’ll have your QA department, managers, and other roles that have different reasons to install different versions of your app.

Having a platform that makes it easy to install different build versions of your app (alpha, staging, prod) and even different versions of those builds (differentiated by build numbers) will allow everybody to do their job well. This is particularly true for QA where they’ll want to install specific builds to test new features or bug fixes.

Platforms like AppCircle offer ways to allow teams to download and test specific builds as needed. I’ve found that AppCircle’s approach to this works as well as you’d expect and has the ability to create different groups of users and assign specific builds to them. This means that you can send QA very specific and testable builds of your app while your managers only have access to beta builds that are almost ready to go to production.

If you’re working within a large company that requires enterprise-level access control and data ownership, let’s take a look at how AppCircle solves this for their enterprise customers.

The importance of security and data ownership

The more people have access to your user’s data and your app’s experimental and in-development features, the more security risks you’re taking on. Limiting access to data and app builds is an essential feature. When you’re looking for a platform that runs your builds and hosts your test binaries it’s essential that you make sure that the platform’s security features align with your needs.

When you require enterprise features, AppCircle has got you. They have very granular access controls which I think is an essential feature.

Enterprise customers for AppCircle all have access to SSO which in corporate environments has always been something that I’ve seen listed as a must-have. At the moment AppCircle offers LDAP as SSO provider but they’re working on Okta integration at the moment. And if your company uses a different SSO provider I know that AppCircle are always open to getting more SSO providers into their product.

SSO for enterprise is an absolute must have since a corporation wants to be able to shut down or lock accounts with a single step and not worry about which other accounts a user might have; they want to manage their users and the services they access in a single place. Less fragmentation in this sense means less risk of security breaches.

Most importantly, it might be absolutely crucial for you to be able to self-host services so that you can make sure that not just your accounts but also your data are completely protected using standards and tools that your company uses and requires.

Large players like GitHub and Atlassian offer this and so does AppCircle.

You can host AppCircle on servers you own while retaining access to first-class support that’s provided through a Slack channel that gives you access to experts directly. This is something that I haven’t encountered before and I think it’s really powerful that AppCircle does this to help keep their enterprise customers going.

Self-hosting’s biggest drawback is always that you’re taking on cost, effort, and risk to make sure your instances keep running. I was pretty impressed to learn that AppCircle goes to great lengths to help reduce each of these three drawbacks by providing the best support they possibly can.

Automating builds and app delivery

While it’s great that AppCircle provides all these enterprise features that I’ve mentioned above, their core business is to become your build and app delivery system. The features they provide for this are exactly what you’d hope for. You can connect AppCircle to your git repository, automatically trigger builds on push or PR creation, and you can run periodic builds to provide nightly alpha’s for example.

The pipelines you build with AppCircle integrate all the way from your git repository to their enterprise app store (where employees can download your internal apps from), their beta testing platform, and even to App Store delivery. All in all they provide a good experience setting this up with reliable builds and they really go to great lengths to make sure that their CI is everything you expect from a good CI provider.

In Summary

As mentioned in the introduction, a company’s needs change as the company grows in terms of complexity. Once you hit a point where you can consider yourself an enterprise developer, it makes sense to start picking your service providers more carefully.

You’ll require fast and reliable support, advanced security measures, granular user and account management, sometimes you’ll even need to have the service running on servers that you own.

AppCircle can help you do all of this and it’s honestly an impressive product that’s growing and improving rapidly. The mix of app distribution, analytics, and CI that they offer is super useful and if I were to request more I would love to see crash reporting be a part of AppCircle too so that you can fully rely on an on-premises AppCircle instance that works for all your infrastructure needs without sending your data to a server you don’t own or control.

If you’d like to learn more about AppCircle and see whether it makes sense for you and your company to switch your infrastructure please let me know so I can get you connected to the right people for a demo and a chat.