What is the “some” keyword in Swift?

If you have spent some time with SwiftUI or if you have watched the WWDC videos on SwiftUI this year, you may have noticed that views in SwiftUI have a property called body of type some View. The some keyword is new in Swift 5.1 and it’s part of a feature called opaque result types (SE-0244). What is this some keyword then? And how can you use it in your code?

I aim to answer these questions in this blog post. We’ll first explore what opaque result types are, and more specifically what problem they solve. Next, we’ll look at how opaque result types are used in SwiftUI and we’ll discover whether it’s a Swift feature that you’re likely to adopt in your code at some point.

Exploring opaque result types

To fully understand the problems solved by opaque result types, it’s good to have a solid understanding of generics. If you’re not familiar with generics at all, I recommend reading these to posts I wrote to get yourself up to speed:

If you’re not interested in learning loads about generics and just want to learn about opaque result types and what the some keyword is, that’s fine too. Just be aware that some of the content in this post could be confusing without understanding generics.

In Swift, we can use protocols to define interfaces or contracts for our objects. When something conforms to a protocol, we know that it can do certain things, or has certain properties. This means that you can write code like this:

protocol ListItemDisplayable {
  var name: String { get }
}

struct Shoe: ListItemDisplayable {
  let name: String
}

var listItem: ListItemDisplayable = Shoe(name: "a shoe")

When using this listItem property, only the properties exposed by ListItemDisplayable are exposed to us. This is especially useful when you want to have an array of items that are ListItemDisplayable where the concrete types can be more than just Shoe:

struct Shoe: ListItemDisplayable {
  let name: String
}

struct Shorts: ListItemDisplayable {
  let name: String
}

var mixedList: [ListItemDisplayable] = [Shoe(name: "a shoe"),
                                        Shorts(name: "a pair of shorts")]

The compiler treats our Shoe and Shorts objects as ListItemDisplayable, so users of this list won't know whether they’re dealing with shoes, shorts, jeans or anything else. All they know is that whatever is in the array can be displayed in a list because it conforms to ListDisplayable.

Opaque result types for protocols with associated types

The flexibility shown in the previous section is really cool, but we can push our code further:

protocol ListDataSource {
  associatedtype ListItem: ListItemDisplayable

  var items: [ListItem] { get }
  var numberOfItems: Int { get }
  func itemAt(_ index: Int) -> ListItem
}

The above defines a ListDataSource that holds some list of an item that conforms to ListItemDisplayable. We can use objects that conform to this protocol as data source objects for table views, or collection views which is pretty neat.

We can define a view model generator object that will, depending on what kind of items we pass it, generate a ListDataSource:

struct ShoesDataSource: ListDataSource {
  let items: [Shoe]
  var numberOfItems: Int { items.count }

  func itemAt(_ index: Int) -> Shoe {
    return items[index]
  }
}

struct ViewModelGenerator {
  func listProvider(for items: [Shoe]) -> ListDataSource {
    return ShoesDataSource(items: items)
  }
}

However, this code doesn’t compile because ListDataSource is a protocol with associated type constraints. We could fix this by specifying ShoesDataSource as the return type instead of ListDataSource, but this would expose an implementation detail that we want to hide from users of the ViewModelGenerator. Callers of listProvider(for:) only really need to know is that we’re going to return a ListDataSource from this method. We can rewrite the generator as follows to make our code compile:

struct ViewModelGenerator {
  func listProvider(for items: [Shoe]) -> some ListDataSource {
    return ShoesDataSource(items: items)
  }
}

By using the some keyword, the compiler can enforce a couple of things while hiding them from the caller of listProvider(for:):

  • We return something that conforms to ListDataSource.
  • The returned object’s associated type matches any requirements that are set by ListDataSource.
  • We always return the same type from listProvider(for:).

Especially this last point is interesting. In Swift, we rely on the compiler to do a lot of compile-time type checks to help us write safe and consistent code. And in turn, the compiler uses all of this information about types to optimize our code to ensure it runs as fast as possible. Protocols are often a problem for the compiler because they imply a certain dynamism that makes it hard for the compiler to make certain optimizations at compile time which means that we’ll take a (very small) performance hit at runtime because the runtime will need to do some type checking to make sure that what’s happening is valid.

Because the Swift compiler can enforce the things listed above, it can make the same optimizations that it can when we would use concrete types, yet we have the power of hiding the concrete type from the caller of a function or property that returns an opaque type.

Opaque result types and Self requirements

Because the compiler can enforce type constraints compile time, we can do other interesting things. For example, we can compare items that are returned as opaque types while we cannot do the same with protocols. Let’s look at a simple example:

protocol ListItemDisplayable: Equatable {
  var name: String { get }
}

func createAnItem() -> ListItemDisplayable {
  return Shoe(name: "a comparable shoe: \(UUID().uuidString)")
}

The above doesn’t compile because Equatable has a Self requirement. It wants to compare two instances of Self where both instances are of the same type. This means that we can’t use ListItemDisplayable as a regular return type, because a protocol on its own has no type information. We need the some keyword here so the compiler will figure out and enforce a type for ListItemDisplayable when we call createAnItem():

func createAnItem() -> some ListItemDisplayable {
  return Shoe(name: "a comparable shoe: \(UUID().uuidString)")
}

The compiler can now determine that we’ll always return Shoe from this function, which means that it knows what Self for the item that’s returned by createAnItem(), which means that the item can be considered Equatable. This means that the following code can now be used to create two items and compare them:

let left = createAnItem()
let right = createAnItem()

print(left == right)

What’s really cool here is that both left and right hide all of their type information. If you call createAnItem(), all you know is that you get a list item back. And that you can compare that list item to other list items returned by the same function.

Opaque return types as reverse generics

The Swift documentation on opaque result types sometimes refers to them as reverse generics which is a pretty good description. Before opaque result types, the only way to use protocols with associated types as a return type would have been to place the protocol on a generic constraint for that method. The downside here is that the caller of the method gets to decide the type that’s returned by a function rather than letting the function itself decide:

protocol ListDataSource {
  associatedtype ListItem: ListItemDisplayable

  var items: [ListItem] { get }ƒ
  var numberOfItems: Int { get }
  func itemAt(_ index: Int) -> ListItem

  init(items: [ListItem])
}

func createViewModel<T: ListDataSource>(for list: [T.ListItem]) -> T {
  return T.init(items: list)
}

func createOpaqueViewModel<T: ListItemDisplayable>(for list: [T]) -> some ListDataSource {
  return GenericViewModel<T>(items: list)
}

let shoes: GenericViewModel<Shoe> = createViewModel(for: shoeList)
let opaqueShoes = createOpaqueViewModel(for: shoeList)

Both methods in the preceding code return the exact same GenericViewModel. The main difference here is that in the first case, the caller decides that it wants to have a GenericViewModel<Shoe> for its list of shoes, and it will get a concrete type back of type GenericViewModel<Shoe>. In the example that uses some, the caller only knows that it will get some ListDataSource that holds its list of ListItemDisplayable items. This means that the implementation of createOpaqueViewModel can now decide what it wants to do. In this case, we chose to return a generic view model. We could also have chosen to return a different kind of view model instead, all that matters is that we always return the same type from within the function body and that the returned object conforms to ListDataSource.

Using opaque return types in your projects

While I was studying opaque return types and trying to come up with examples for this post, I noticed that it’s not really easy to come up with reasons to use opaque return types in common projects. In SwiftUI they serve a key role, which might make you believe that opaque return types are going to be commonplace in a lot of projects at some point.

Personally, I don’t think this will be the case. Opaque return types are a solution to a very specific problem in a domain that most of us don’t work on. If you’re building frameworks or highly reusable code that should work across many projects and codebases, opaque result types will interest you. You’ll likely want to write flexible code based on protocols with associated types where you, as the builder of the framework, have full control of the concrete types that are returned without exposing any generics to your callers.

Another consideration for opaque return types might be their runtime performance. As discussed earlier, protocols sometimes force the compiler to defer certain checks and lookups until runtime which comes with a performance penalty. Opaque return types can help the compiler make compile-time optimizations which is really cool, but I’m confident that it won’t matter much for most applications. Unless you’re writing code that really has to be optimized to its core, I don’t think the runtime performance penalty is significant enough to throw opaque result types at your codebase. Unless, of course, it makes a lot of sense to you. Or if you’re certain that in your case the performance benefits are worth it.

What I’m really trying to say here is that protocols as return types aren’t suddenly horrible for performance. In fact, they sometimes are the only way to achieve the level of flexibility you need. For example, if you need to return more than one concrete type from your function, depending on certain parameters. You can’t do that with opaque return types.

This brings me to quite possibly the least interesting yet easiest way to start using opaque return types in your code. If you have places in your code where you’ve specified a protocol as return type, but you know that you’re only returning one kind of concrete type from that function, it makes sense to use an opaque return type instead. In fact, the Swift team is considering inferring some whenever you use a protocol as a type in Swift 6.0. This might never make it into Swift 6.0, but it does show that the Swift team is serious about some being a good default to try whenever you can.

A more interesting consideration to make for using some is in places where you've defined a single use generic. For example, in the following situation you might be able to use some instead of a generic:

class MusicPlayer {
  func play<Playlist: Collection<Track>>(_ playlist: Playlist) { /* ... */ }
}

In this example, our play function has a generic argument Playlist that's constrained to a Collection that holds Track objects. We can write this constraint thanks to Swift 5.7's primary associated types. Learn more about primary associated types in this post. If we only use the Playlist generic in a single place like a function argument, we can use some instead of the generic from Swift 5.7 onward. Swift 5.7 allows us to use some for function arguments which is a huge improvement.

Rewriting the example above with some looks as follows:

class MusicPlayer {
  func play(_ playlist: some Collection<Track>) { /* ... */ }
}

Much better, right?

In summary

In this post you saw what problems opaque return types solve, and how they can be used by showing you several examples. You learned that opaque return types can act as a return type if you want to return an object that conforms to a protocol with associated type constraints. This works because the compiler performs several checks at compile time to figure out what the real types of a protocol’s associated types are. You also saw that opaque return types help resolve so-called Self requirements for similar reasons. Next, you saw how opaque result types act as reverse generics in certain cases, which allows the implementer of a method to determine a return type that conforms to a protocol rather than letting the caller of the method decide.

Next, I gave some insights into what opaque result types are likely going to in your apps. With Swift 5.7's ability to use some in more places than just return types I think some will become a very useful tool that will help us use conrete types instead of existentials (protocols) in lots of places which should make our code more performant and robust.

If you have any questions, feedback or if you have awesome applications of opaque return types that I haven’t covered in this post, I would love to hear from you on Twitter.

Generics in Swift explained

Whenever we write code, we want our code to be well-designed. We want it to be flexible, elegant and safe. We want to make sure that Swift’s type system and the compiler catch as many of our mistakes as possible. It’s especially interesting how Swift’s type system can help us avoid obvious errors. For example, Swift won’t allow you to assign an Int to a String property like this:

var anInt = 10
anInt = "Hello, World!"

The Swift compiler would show you an error that explains that you can’t assign a String to an Int and you’d understand this. If something is declared or inferred to be a certain type, it can’t hold any types other than the one it started out as.

It’s not always that simple though. Sometimes you need to write code where you really don’t know what type you’re working with. All you know is that you want to make sure that no matter what happens, that type cannot change. If this doesn’t sound familiar to you, or you’re wondering who in their right mind would ever want that, keep reading. This article is for you.

Reverse engineering Array

A great example of an object that needs the flexibility that I described earlier is an array. Considering that arrays in Swift are created to hold objects of a specific type, whether it’s a concrete type or a protocol, array’s aren’t that different from the mistake I showed you earlier. Let’s adapt the example to arrays so you can see what I mean:

var arrayOfInt = [1, 2, 3]
arrayOfInt = ["one", "two", "three"]

If you try to run this code you will see an error that explains you can’t assign an object of Array<String> to Array<Int>. And this is exactly the kind of magic that you need generics for.

Arrays are created in such a way that they work with any type you throw at them. The only condition being that the array is homogenous, in other words, it can only contain objects of a single type.

So how is this defined in Swift? What does a generic object like Array look like? Instead of showing you the exact implementation from the Swift standard library, I will show you a simplified version of it:

public struct Array<Element> {
  // complicated code that we don’t care about right now
}

The interesting part here is between the angle brackets: <Element>. The type Element does not exist in the Swift standard library. It’s a made-up type that only exists in the context of arrays. It specifies a placeholder that’s used where the real, concrete type would normally be used.

Let’s build a little wrapper around Array that will help you make sense of this a little bit more.

struct WrappedArray<OurElement> {
  private var array = Array<OurElement>()

  mutating func append(_ item: OurElement) {
    array.append(item)
  }

  func get(atIndex index: Int) -> OurElement {
    return array[index]
  }
}

Notice how instead of Element, we use the name OurElement. This is just to prove that Element really doesn’t exist in Swift. In the body of this struct, we create an array. We do this by using its fully written type Array<OurElement>(). The same can be achieved using the following notation: [OurElement](). The outcome is the same.

Next, in the append and get methods we accept and return OurElement respectively. We don’t know what OurElement will be. All we know is that the items in our array, the items we append to it and the items we retrieve from it, will all have the same type.

To use your simple array wrapper you might write something like this:

var myWrappedArray = WrappedArray<String>
myWrappedArray.append("Hello")
myWrappedArray.append("World")
let hello = myWrappedArray.get(atIndex: 0) // "Hello"
let world = myWrappedArray.get(atIndex: 1) // "World"

Neat stuff, right! Try adding an Int, or something else to myWrappedArray. Swift won’t let you, because you specified that OurElement can only ever be String for myWrappedArray by placing String between the angle brackets.

You can create wrapped arrays that hold other types by placing different types between the angle brackets. You can even use protocols instead of concrete types:

var codableArray = WrappedArray<Codable>

The above would allow you to add all kinds of Codable objects to codableArray. Note that if you try to retrieve them from the array using get, you will get a Codable object back, not the conforming type you might expect:

var codableArray = WrappedArray<Codable>

let somethingCodable = Person()
codableArray.append(somethingCodable)
let item = codableArray.get(0) // item is Codable, not Person

The reason for this is that get returns OurElement and you specified OurElement to be Codable.

Similar to arrays, Swift has generic objects for Set (Set<Element>), Dictionary (Dictionary<Key, Value>) and many other objects. Keep in mind that whenever you see something between angle brackets, it’s a generic type, not a real type.

Before we look at an example of generics that you might be able to use in your own code someday, I want to show you that functions can also specify their own generic parameters. A good example of this is the decode method on JSONDecoder:

func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
  // decoding logic
}

If you’ve never used this method yourself, it would normally be called as follows:

let result = try? decoder.decode(SomeType.self, from: data) // result is SomeType

Let’s pick apart the method signature for decode a bit:

  • func decode<T>: the decode method specifies that it uses a generic object called T. Again, T is only a placeholder for whatever the real type will be, just like Element and OurElement were in earlier examples.
  • (_ type: T.Type, from data: Data): one of the arguments here is T.Type. This means that we must call this method and specify the type we want to decode data into. In the example, I used SomeType.self. When you call the method with SomeType.self without explicitly specifying T, Swift can infer that T will now be SomeType.
  • throws -> T: This bit marks decode as throwing and it specifies that decode will return T. In the example, T was inferred to be SomeType.
  • where T : Decodable: this last bit of decode's method signature applies a constraint to T. We can make T whatever we want, as long as that object conforms to Decodable. So in our example, we’re only allowed to use SomeType as the type of T if SomeType is decodable.

Take another look at the method signature of decode and let it all sink in for a moment. We’re going to build our own struct in a moment that will put everything together so if it doesn’t make sense yet, I hope it does after the next section.

Applying generics in your code

You have seen how Array and JSONDecoder.decode use generics. Let’s build something relatively simple that applies your newfound logic using an example that I have run into many times over the years.

Imagine you’re building an app that shows items in table views. And because you like to abstract things and separate concerns, you have taken some of your UITableViewDataSource logic and you’ve split that into a view model and the data source logic itself. Yes, I said view model and no, we’re not going to talk about architecture. View models are just a nice way to practice building something with generics for now.

In your app you might have a couple of lists that expose their data in similar ways and heavily simplified, your code might look like this:

struct ProductsViewModel {
  private var items: [Products]
  var numberOfItems: Int { items.count }

  func item(at indexPath: IndexPath) -> Products {
    return items[indexPath.row]
  }
}

struct FavoritesViewModel {
  private var items: [Favorite]
  var numberOfItems: Int { items.count }

  func item(at indexPath: IndexPath) -> Favorite {
    return items[indexPath.row]
  }
}

This code is really repetitive, isn’t it? Both view models have similar property and method names, the only real difference is the type of the objects they operate on. Look back to our WrappedArray example. Can you figure out how to use generics to make these view models less repetitive?

If not, that’s okay. Here’s the solution:

struct ListViewModel<Item> {
  private var items: [Item]
  var numberOfItems: Int { items.count }

  func item(at indexPath: IndexPath) -> Item {
    return item[indexPath.row]
  }
}

Neat, right! And instead of the following code:

let viewModel = FavoritesViewModel()

You can now write:

let viewModel = ListViewModel<Favorite>()

The changes in your code are minimal, but you’ve removed code duplication which is great! Less code duplication means fewer surface areas for those nasty bugs to land on.

One downside of the approach is that you can now use any type of object as Item, not just Favorite and Product. Let’s fix this by introducing a simple protocol and constraining ListViewModel so it only accepts valid list items as Item:

protocol ListItem {}
extension Favorite: ListItem {}
extension Product: ListItem {}

struct ListViewModel<Item> where Item: ListItem {
  // implementation
}

Of course, you can decide to add certain requirements to your ListItem protocol but for our current purposes, an empty protocol and some extensions do the trick. Similar to how decode was constrained to only accept Decodable types for T, we have now constrained ListViewModel to only allow types that conform to ListItem as Item.

Note
Sometimes the where is moved into the angle brackets: struct ListViewModel<Item: ListItem> the resulting code functions exactly the same and there are no differences in how Swift compiles either notation.

In summary

In this blog post, you learned where the need for generics come from by looking at type safety in Swift and how Array makes sure it only contains items of a single type. You created a wrapper around Array to experiment with generics and saw that generics are placeholders for types that are filled in at a later time. Next, you saw that functions can also have generic parameters and that they can be constrained to limit the types that can be used to fill in the generic.

To tie it all together you saw how you can use generics and generic constraints to clean up some duplicated view model code that you actually might have in your projects.

All in all, generics are not easy. It’s okay if you have to come back to this post every now and then to refresh your memory. Eventually, you’ll get the hang of it! If you have questions, remarks or just want to reach out to me, you can find me on Twitter.

Efficiently loading images in table views and collection views

When your app shows images from the network in a table view or collection view, you need to load the images asynchronously to make sure your list scrolls smoothly. More importantly, you’ll need to somehow connect the image that you’re loading to the correct cell in your list (instead of table view or collection view, I’m going to say list from now on). And if the cell goes out of view and is reused to display new data with a new image, you’ll want to cancel the in-progress image load to make sure new images are loaded immediately. And, to make sure we don’t go to the network more often than needed, we’ll need some way to cache images in memory, or on disk, so we can use the local version of an image if we’ve already fetched it in the past. Based on this, we can identify three core problems that we need to solve when loading images for our cells asynchronously:

  1. Setting the loaded image on the correct cell.
  2. Canceling an in-progress load when a cell is reused.
  3. Caching loaded images to avoid unneeded network calls.

In this post, I’m going to show you how to build a simple image loader class, and write a table view cell that will help us solve all these problems. I will also show you how you can use the same image loader to enhance UIImage with some fancy helpers.

The loader and techniques in this post do not use Swift's async/await. If you're interested in building an image loader that leverages async/await make sure you check out this post alongside this one.

Building a simple image loader

When you make a GET request using URLSession, you typically do so through a data task. Normally, you don’t hold on to that data task because you don’t need it. But if you keep a reference to your data task around, you can cancel it at a later time. I’m going to use a dictionary of [UUID: URLSessionDataTask] in the image loader we’re building because that will allow me to keep track of running downloads and cancel them later. I’m also going to use a dictionary of [URL: UIImage] as a simple in-memory cache for loaded images. Based on this, we can begin writing the image loader:

class ImageLoader {
  private var loadedImages = [URL: UIImage]()
  private var runningRequests = [UUID: URLSessionDataTask]() 
}

We can also implement a loadImage(_:completion:) method. This method will accept a URL and a completion handler, and it’s going to return a UUID that’s used to uniquely identify each data task later on. The implementation looks as follows:

func loadImage(_ url: URL, _ completion: @escaping (Result<UIImage, Error>) -> Void) -> UUID? {

  // 1
  if let image = loadedImages[url] {
    completion(.success(image))
    return nil
  }

  // 2
  let uuid = UUID()

  let task = URLSession.shared.dataTask(with: url) { data, response, error in
    // 3
    defer {self.runningRequests.removeValue(forKey: uuid) }

    // 4
    if let data = data, let image = UIImage(data: data) {
      self.loadedImages[url] = image
      completion(.success(image))
      return
    }

    // 5
    guard let error = error else {
      // without an image or an error, we'll just ignore this for now
      // you could add your own special error cases for this scenario
      return
    }

    guard (error as NSError).code == NSURLErrorCancelled else {
      completion(.failure(error))
      return
    }

    // the request was cancelled, no need to call the callback
  }
  task.resume()

  // 6
  runningRequests[uuid] = task
  return uuid
}

Let’s go over the preceding code step by step, following the numbered comments.

  1. If the URL already exists as a key in our in-memory cache, we can immediately call the completion handler. Since there is no active task and nothing to cancel later, we can return nil instead of a UUID instance.
  2. We create a UUID instance that is used to identify the data task that we’re about to create.
  3. When the data task completed, it should be removed from the running requests dictionary. We use a defer statement here to remove the running task before we leave the scope of the data task’s completion handler.
  4. When the data task completes and we can extract an image from the result of the data task, it is cached in the in-memory cache and the completion handler is called with the loaded image. After this, we can return from the data task’s completion handler.
  5. If we receive an error, we check whether the error is due to the task being canceled. If the error is anything other than canceling the task, we forward that to the caller of loadImage(_:completion:).
  6. The data task is stored in the running requests dictionary using the UUID that was created in step 2. This UUID is then returned to the caller.

Note that steps 3 through 5 all take place in the data task’s completion handler. This means that the order in which the listed steps execute isn’t linear. Step 1 and 2 are executed first, then step 6 and then 3 through 5.

Now that we have logic to load our images, let’s at some logic that allows us to cancel in-progress image downloads too:

func cancelLoad(_ uuid: UUID) {
  runningRequests[uuid]?.cancel()
  runningRequests.removeValue(forKey: uuid)
}

This method receives a UUID, uses it to find a running data task and cancels that task. It also removes the task from the running tasks dictionary, if it exists. Fairly straightforward, right?

Let’s see how you would use this loader in a table view’s cellForRowAtIndexPath method:

// 1
let token = loader.loadImage(imageUrl) { result in
  do {
    // 2
    let image = try result.get()
    // 3
    DispatchQueue.main.async {
      cell.cellImageView.image = image
    }
  } catch {
    // 4
    print(error)
  }
}

// 5
cell.onReuse = {
  if let token = token {
    self.loader.cancelLoad(token)
  }
}

Let’s go through the preceding code step by step again:

  1. The image loader’s loadImage(_:completion:) method is called, and the UUID returned by the loader is stored in a constant.
  2. In the completion handler for loadImage(_:completion:), we extract the result from the completion’s result argument.
  3. If we successfully extracted an image, we dispatch to the main queue and set the fetched image on the cell’s imageView property. Not sure what dispatching to the main queue is? Read more in this post
  4. If something went wrong, print the error. You might want to do something else here in your app.
  5. I’ll show you an example of my cell subclass shortly. The important bit is that we use the UUID that we received from loadImage(_:completion:) to cancel the loader’s load operation for that UUID.

Note that we do this in the cellForRowAt method. This means that every time we’re asked for a cell to show in our list, this method is called for that cell. So the load and cancel are pretty tightly coupled to the cell’s life cycle which is exactly what we want in this case. Let’s see what onReuse is in a sample cell:

class ImageCell: UITableViewCell {
  @IBOutlet var cellImageView: UIImageView!
  var onReuse: () -> Void = {}

  override func prepareForReuse() {
    super.prepareForReuse()
    onReuse()
    cellImageView.image = nil
  }
}

The onReuse property is a closure that we call when the cell’s prepareForReuse method is called. We also remove the current image from the cell in prepareForReuse so it doesn’t show an old image while loading a new one. Cells are reused quite often so doing the appropriate cleanup in prepareForReuse is crucial to prevent artifacts from old data on a cell from showing up when you don’t want to.

If you implement all of this in your app, you’ll have a decent strategy for loading images. You would probably want to add a listener for memory warnings that are emitted by your app’s Notification Center, and maybe you would want to cache images to disk as well as memory too, but I don’t think that fits nicely into the scope of this article for now. Keep these two features in mind though if you want to implement your own image loader. Especially listening for memory warnings is important since your app might be killed by the OS if it consumes too much memory by storing images in the in-memory cache.

Enhancing UIImageView to create a beautiful image loading API

Before we implement the fancy helpers, let’s refactor our cell and cellForRowAt method so they already contain the code we want to write. The prepareForReuse method is going to look as follows:

override func prepareForReuse() {
  cellImageView.image = nil
  cellImageView.cancelImageLoad()
}

This will set the current image to nil and tells the image view to stop loading the image it was loading. All of the image loading code in cellForRowAt should be replaced with the following:

cell.cellImageView.loadImage(at: imageUrl)

Yes, all of that code we had before is now a single line.

To make this new way of loading and canceling work, we’re going to implement a special image loader class called UIImageLoader. It will be a singleton object that manages loading for all UIImageView instances in your app which means that you end up using a single cache for your entire app. Normally you might not want that, but in this case, I think it makes sense. The following code outlines the skeleton of the UIImageLoader:

class UIImageLoader {
  static let loader = UIImageLoader()

  private let imageLoader = ImageLoader()
  private var uuidMap = [UIImageView: UUID]()

  private init() {}

  func load(_ url: URL, for imageView: UIImageView) {

  }

  func cancel(for imageView: UIImageView) {

  }
}

The loader itself is a static instance, and it uses the ImageLoader from the previous section to actually load the images and cache them. We also have a dictionary of [UIImageView: UUID] to keep track of currently active image loading tasks. We map these based on the UIImageView so we can connect individual task identifiers to UIImageView instances.

The implementation for the load(_:for:) method looks as follows:

func load(_ url: URL, for imageView: UIImageView) {
  // 1
  let token = imageLoader.loadImage(url) { result in
    // 2
    defer { self.uuidMap.removeValue(forKey: imageView) }
    do {
      // 3
      let image = try result.get()
      DispatchQueue.main.async {
        imageView.image = image
      }
    } catch {
      // handle the error
    }
  }

  // 4
  if let token = token {
    uuidMap[imageView] = token
  }
}

Step by step, this code does the following:

  1. We initiate the image load using the URL that was passed too load(_:for:).
  2. When the load is completed, we need to clean up the uuidMap by removing the UIImageView for which we’re loading the image from the dictionary.
  3. This is similar to what was done in cellForRowAt before. The image is extracted from the result and set on the image view itself.
  4. Lastly, if we received a token from the image loader, we keep it around in the [UIImageView: UUID] dictionary so we can reference it later if the load has to be canceled.

The cancel(for:) method has the following implementation:

func cancel(for imageView: UIImageView) {
  if let uuid = uuidMap[imageView] {
    imageLoader.cancelLoad(uuid)
    uuidMap.removeValue(forKey: imageView)
  }
}

If we have an active download for the passed image view, it’s canceled and removed from the uuidMap. Very similar to what you’ve seen before.

All we need to do now is add an extension to UIImageView to add the loadImage(at:) and cancelImageLoad() method you saw earlier:

extension UIImageView {
  func loadImage(at url: URL) {
    UIImageLoader.loader.load(url, for: self)
  }

  func cancelImageLoad() {
    UIImageLoader.loader.cancel(for: self)
  }
}

Both methods pass self to the image loader. Since the extension methods are added to instances of UIImageView, this helps the image loader to connect the URL that it loads to the UIImageView instance that we want to show the image, leaving us with a very simple and easy to use API! Cool stuff, right?

What’s even better is that this new strategy can also be used for images that are not in a table view cell or collection view cell. It can be used for literally any image view in your app!

In summary

Asynchronously loading data can be a tough problem on its own. When paired with the fleeting nature of table view (and collection view) cells, you run into a whole new range of issues. In this post, you saw how you can use URLSession and a very simple in-memory cache to implement a smart mechanism to start, finish and cancel image downloads.

After creating a simple mechanism, you saw how to create an extra loader object and some extensions for UIImageView to create a very straightforward and easy to use API to load images from URLs directly into your image views.

Keep in mind that the implementations I’ve shown you here aren’t production-ready. You’ll need to do some work in terms of memory management and possibly add a disk cache to make these objects ready for prime time.

If you have any questions about this topic, have feedback or anything else, don’t hesitate to shoot me a message on Twitter.

Appropriately using DispatchQueue.main

Lots of iOS developers eventually run into code that calls upon DispatchQueue.main. It's often clear that this is done to update the UI, but I've seen more than a handful of cases where developers use DispatchQueue.main as an attempt to get their code to work if the UI doesn't update as they expect, or if they run into crashes they don't understand. For that reason, I would like to dedicate this post to the question "When should I use DispatchQueue.main? And Why?".

Understanding what the main dispatch queue does

In iOS, we use dispatch queues to perform work in parallel. This means that you can have several dispatch queues running at the same time, and they can all be performing tasks simultaneously. In general, dispatch queues will only perform one task at a time in a first-in, first-out kind of fashion. They can, however, be configured to schedule work concurrently.

The main dispatch queue is a queue that runs one task at a time. It's also the queue that performs all layout updates. If somebody talks about the importance of not blocking the main thread, what they're really saying is that they don't want to keep the main dispatch queue busy for too long. If you keep the main queue busy too long, you will notice that your application's scroll performance becomes choppy, animations stutter and buttons become unresponsive.

The reason for this is that the main queue is responsible for everything UI related, but like we already established, the main queue is a serial queue. So if the main queue is busy doing something, it can't respond to user input or draw new frames of your animation.

A lot of code in iOS is code that can take a while to run. For example, making a network request is a good example of code that is very slow to execute. Once the network call is sent off to the server, the code has to wait for a response. While waiting, that queue isn't doing anything else. When the response comes back a couple of seconds later, the queue can process the results and move on to the next task. If you would perform this work on the main queue, your app wouldn't be able to draw any UI or respond to user input for the entire duration of the network request.

We can summarise this whole section in just a short sentence. The main queue is responsible for drawing UI and responding to user input. In the next section, we'll take a closer look at when we should use DispatchQueue.main and what that does exactly.

Using DispatchQueue.main in practice

Sticking with the example of making a network call, you can assume that network calls are made on their own queue, away from the main queue. Before we continue, I want to refresh your memory and show you what the code for a network call looks like:

URLSession.shared.dataTask(with: someURL) { data, response, error in 

}

The data task is created with a completion closure which is called when the network call completes. Since the data that's returned by the server might still need to be processed, iOS calls your completion closure on a background queue. Usually, this is great. I don't think there's any situation where you won't want to do any processing to the data that you receive from the server at all. Sometimes you might have to do more processing than other times, but no processing at all is very rare. Once you're done processing the data however, you'll probably want to update your UI.

Since you know you're not on the main queue when handling a network response, you'll need to use DipatchQueue.main to make sure your UI updates on the main queue. The following code is an example of reloading a table view on the main queue.

DispatchQueue.main.async {
  self.tableView.reload()
}

This code looks simple enough, right? But what's really going on here?

DispatchQueue.main is an instance of DispatchQueue. All dispatch queues can schedule their work to be executed sync or async. Typically you will want to schedule work async because scheduling your work synchronously would halt the execution of the current thread, wait for the target thread execute the closure that you pass to sync, and then resume the current thread. Using async allows the current thread to resume while the target thread can schedule and perform your closure when needed. Let's look at an example:

func executesAsync() {
  var result = 0

  DispatchQueue.global().async {
    result = 10 * 10
  }

  print(result) // 0
}

func executesSync() {
  var result = 0

  DispatchQueue.global().sync {
    result = 10 * 10
  }

  print(result) // 100
}

Both of the preceding functions look very similar. The main difference here is that executesAsync dispatches to the main queue asynchronously, causing result to be printed before, result is updated. The executesSync function dispatches to the main queue synchronously, which results in the execution of executesSync to be paused until the closure passed to DispatchQueue.main.sync finishes executing. This means that result is updated when print is called.

Think about the preceding example in the context of reloading a table view. If we would use sync instead of async, the executing of the network call completion closure is paused while the main thread reloads the table view. Once the table view is reloaded, the execution of the closure is resumed. By using async, the completion closure continues its execution and the table view will reload whenever the main queue has time to do so. This, hopefully, is pretty much instantaneously because if it takes a while it's probably a symptom of blocking the main thread.

Knowing when to use DispatchQueue.main

Now that you know what the main queue is, and you've seen an example of how and when DispatchQueue.main is used, how do you know when you should use DispatchQueue.main in your project?

The simplest answer is to always use it when you're updating UI in a delegate method or completion closure because you don't control how or when that code is called. In addition, Xcode will crash your app if it detects that you're doing UI work away from the main thread. While this is a very convenient feature that has helped me prevent bugs every now and then, it's not reliable 100% of the time and there are better ways to make sure your code runs on the main thread when it has to.

Remember to always dispatch your queue to the main queue asynchronously using DispatchQueue.main.async to avoid blocking the current thread. And potentially even deadlocking your app, which can happen if you call DispatchQueue.main.sync from code that is already on the main queue. Dispatching to the main queue with async does not carry this same risk, even if you're already on the main queue.

Let's look at one last example. If you fetch a user's current push notification permissions or request their contacts, you know that operation runs asynchronously and it might take a while. If you want to update the UI in the completion closure that's used for these operations, it's best to explicitly make sure your UI updates are done on the main queue by wrapping your UI updates in a DispatchQueue.main.async block.

In Summary

Writing applications that use multiple queues can be really complicated. As a general rule, keep in mind that the main queue is reserved for UI work. That doesn't mean that all non-UI work has to go off the main queue, but it does mean that all the UI work most be on the main queue and all other work can be somewhere else if you want it to be. For example, if you know an operation might take a while to complete.

In other words, the short answer to the question from the beginning of this article, you should use DispatchQueue.main to send UI related work from non-main queues to the main queue.

If you have questions about this article, feedback, suggestions or anything else, feel free to reach out to me on Twitter

Changes to location access in iOS 13

If you're working on an app that requires access to a user's location, even when your user has sent your app to the background, you might have noticed that when you ask the user for the appropriate permission, iOS 13 shows a different permissions dialog than you might expect. In iOS 12 and below, when you ask for so-called always permissions, the user can choose to allow this, allow location access only in the foreground or they can decide to now allow location access at all. In iOS 13, the user can choose to allow access once, while in use or not at all. The allow always option is missing from the permissions dialog completely.

In this post, you will learn what changes were made to location access in iOS 13, why there is no more allow always option and lastly you'll learn what allow once means for your app. We have a lot to cover so let's dive right in.

Asking for background location permissions in iOS 13

The basic rules and principles of asking for location permissions in applications haven't changed since iOS 12. So all of the code you wrote to ask a user for location permissions should still work the same in iOS 13 as it did in iOS 12. The major differences in location permissions are user-facing. In particular, Apple has doubled down on security and user-friendliness when it comes to background location access. A user's location is extremely privacy-sensitive data and as a developer, you should treat a user's location with extreme caution. For this reason, Apple decided that accessing a user's location in the background deserves a special, context-sensitive prompt rather than a prompt that presented while your app is in the foreground.

If you want to access a user's location in the background, you can ask them for this permission using the following code:

let locationManager = CLLocationManager()

func askAlwaysPermission() {
  locationManager.requestAlwaysAuthorization()
}

This code will cause the location permission dialog to pop up if the current CLAuthorizationStatus is .notDetermined. The user can now choose to deny location access, allow once or to allow access while in use. If the user chooses access while in use, your location manager's delegate will be informed of the choice and the current authorization status will be .authorizedAlways.

But wait. The user chooses when in use! Why are we now able to always access the user's location?

Because Apple is making background location access more user-centered, your app is tricked into thinking it has access to the user's background even if the app is in the background by giving it provisional authorization. You are expected to handle this scenario just like you would normally. Set up your geofencing, start listening for location updates and more. When your app is eventually backgrounded and tries to use the user's location, iOS will wait for an appropriate moment to inform the user that you want to use their location in the background. The user can then choose to either allow this or to deny it.

Note that your app is not aware of this interaction and location events in the background are not delivered to your app until the user has explicitly granted you permission to access their location in the background. This new experience for the user means that you must ensure that your user understands why you need access to their location in the background. If you're building an app that gives a user suggestions for great coffee places near their current location, you probably don't need background location access. All you really need to know is where the user is while using your app so you can give them good suggestions that are nearby, in this example, it's very unlikely that the user will understand why the background permission dialog pops-up and they will deny access.

However, if you're a home automation app that uses geofences to execute a certain home automation when a user enters or leaves a certain area, it makes sense for you to need the always access permission so you can execute a specific automation even if the user isn't actively using your app.

Speaking of geofences and similar APIs that require always authorization in iOS 12, iOS 13 allows you to set up geofences, listen for significant location changes and monitor even if your app only has when in use permission. This allows you to monitor geofences and more as long as your app is active. Usually, your app is active when it's foregrounded but there are exceptions.

If you have an app where you don't really need always authorization but you do want to allow your user to send your app to the background while you access their location, for example, if you're building some kind of scavenger hunt app where a user must enter a geofence that you've set up, you can set the allowsBackgroundLocationUpdates property on your location manager to true and your app will show the blue activity indicator on the user's status bar. While this activity indicator is present, your app remains active and location-related events are delivered to your app.

All in all, you shouldn't have to change much, if anything at all, for the new location permission strategy in iOS 13. Your app's location permissions will change according to the user's preferences just like they always have. The biggest change is in the UI and the fact that the user will not be prompted for always authorization until you actually try to use their location in the background.

Let's look at another change in iOS 13 that enables users to allow your app to access the user's location once.

Understanding what it means when a user allows your app to access their location once

Along with provisional authorization for accessing the user's location in the background, users can now choose to allow your app to access their location once. If a user chooses this option when the location permissions dialog appears, your app will be given the .authorizedWhenInUse authorization status. This means that your app can't detect whether you will always be able to access the user's location when they're using your app, or if you can access their location only for the current session. This, again, is a change that Apple made to improve the user's experience and privacy.

If a user chooses the allow once permission option, your app can access their current location until your app is moved to the background and becomes inactive. If your app becomes active again, you have to ask for location permission again.

It's recommended that you don't ask for permission as soon as the app launches. Instead, think about why you asked for location permission in the first place. The user probably was trying to do something with your app where it made sense to access their location. You should wait for the next moment where it makes sense to access the user's location rather than asking them for permission immediately when your app returns to the foreground.

If you think your app should have access to the user's location even if the app is backgrounded for a while, you can set the location manager's allowsBackgroundLocationUpdates to true and your app's session remains active until the user decides to stop it. This would be appropriate for an app that tracks hikes or runs where a user would expect you to continue actively tracking their location even while their phone is in their pocket.

Similar to provisional authorization, this change shouldn't impact your code too much. If you're already dealing with your user's locations in a careful and considerate way chances are that everything in your will work perfectly fine with the new allow once permission.

In Summary

Apple's efforts to ensure that your user's data is safe and protected are always ongoing. This means that they sometimes make changes to how iOS works that impact our apps in surprising ways and I think that location access in iOS 13 is no exception to this. It can be very surprising when you're developing your app on iOS 13 and things don't work like you're used to.

In this blog post, you learned what changes Apple has made to location permissions, and how they impact your code. You learned that apps now get provisional access to a user's location in the background and that provisional access can be converted to permanent access if you attempt to use a user's location and the background and they allow it. You also learned that your app won't receive any background location events until the user has explicitly allowed this.

In addition to provisional location access, you learned about one-time location access. You saw that your app will think it has the while in use permissions and that those permissions will be gone the next time the user launches your app. This means that you should ensure to ask for location permissions when it makes sense rather than doing this immediately. Poorly timed location permission dialogs are far more likely to result in a negative response from a user than a well thought out experience.

If you have any questions or feedback for about this post or any other post on my blog, make sure to reach out on Twitter.

Using launch arguments for easier Core Data debugging

If you use Core Data in your apps, you might be aware that the larger and more complicated your set up becomes, the harder it is to debug. It's at this point where you might start to get frustrated with Core Data and its black-box kind of implementation. You might think that you simply have to trust that Core Data will do the ideal thing for your app.

Furthermore, you might have a set up with multiple managed object contexts, each confined to its own thread. And when your app crashes sometimes, you think it's related to Core Data in one way or the other, but you're not quite sure how to debug it. Especially because your app only crashes sometimes rather than all the time.

In this post, I want to show you some Core Data related Launch Arguments that will help you debug and validate your Core Data related code. Let's start by using Launch Arguments to see what Core Data does under the hood. Next, we'll see how to detect most of your threading problems in Core Data.

Knowing what Core Data does under the hood

Sometimes when you use Core Data you want to open the underlying SQLite file to see whether your data is actually stored as expected, or maybe you want to inspect the database structure. To do this, you need to know where Core Data has stored your SQLite file, this can be especially challenging when you're running on the simulator. Because let's be honest, we don't know what the UUID of our simulator is and we most certainly don't want to have to figure out its location every time we need to find our SQLite file.

Luckily, you can use a Launch Argument to get Core Data to log some information to the console. To add a Launch Argument, use to top menu in Xcode to go to Product -> Scheme -> Edit Scheme... (cmd + >). Select the Run configuration and go to the Arguments tab as shown in the following screenshot:

Example of the schema window

To get Core Data to log information to the console, click the + button under the Launch Arguments and add the following argument:

-com.apple.CoreData.SQLDebug 1

The result should look as follows:

Screenshot of Core Data debug flags in scheme editor

If you run your Core Data app after setting this Launch Argument, Core Data will start logging basic information like where the backing SQLite file is stored, and what queries are executed.

You can increase the log level all the way up to level 4, at that point Core Data will log pretty much everything you might want to know about what's going on under the hood and more. For example, this might help you notice that Core Data is performing lots of SQLite queries to fetch object relationships. And based on that discovery you might decide that certain fetch requests should automatically fetch certain relationships by setting your fetch request's relationshipKeyPathsForPrefetching property.

In case you're curious, the following list describes the different Core Data SQLDebug log levels:

  1. SQL statements and their execution time
  2. Values that are bound in the statement
  3. Fetched managed object IDs
  4. SQLite EXPLAIN statement

These four log levels give you a lot of information that you can use to improve your apps. Of course, the usefulness of certain log levels like level four depends entirely on your knowledge of SQLite. But even if you're not well versed in SQLite, I recommend to take a look at all of the log levels sometimes, they can produce some interesting outputs.

Detecting threading problems in Core Data

One of the biggest frustrations you might have with Core Data is random crashes due to threading problems. You're supposed to use managed object context and managed objects only on the threads that they were created on, and violating this rule might crash your app. However, usually your app won't crash and everything is fine. But then every now and then a random crash pops up. You can tell that it's Core Data related but you might not be sure where the error is coming from exactly.

Luckily, the Core Data team has thought of a way to help you get rid of these crashes. In Xcode, you can add the -com.apple.CoreData.ConcurrencyDebug 1 Launch Argument to run Core Data in an extra strict mode. Whenever Core Data encounters a threading violation, your app will immediately crash and Xcode will point out the exact line where the violation occurred.

I can recommend everybody to use this Launch Argument in development because it will help you catch threading problems early, and forces you to fix them right away rather than getting some nasty surprises when your app is already published to the App Store.

In Summary

While you can’t eliminate all bugs and performance issues with debug flags, it does help to have some tools available that you can use to make your problems more visible. Whether it's making you app crash if you break Core Data's threading rules, or gaining insights into the different SQLite queries Core Data does under the hood, it's always good to understand how your code behaves under the hood.

I hope these debug flags will help save you loads of time, just like they do for me. If you have questions, feedback or simply want to reach out to me, don't hesitate to contact me on Twitter

Adding haptic feedback to your app with CoreHaptics

One of iOS 13's more subtle, yet amazingly fun and powerful frameworks is CoreHaptics. With this framework, you can add tactile feedback to your app. When implemented correctly, this kind of feedback will delight and amaze your users. It will make your app feel alive like it's physically responsive to the touch input from your user. If you want to see what haptic feedback feels like in a real app, check out the CocoaHub app. It uses haptic feedback when you switch tabs in the app and in my opinion, this feels really nice.

In this blog post you will learn about the following:

  • Understanding the different types of haptic feedback
  • Adding haptic feedback to your app
  • Using AHAP files to define your haptics

By the end of this post, you should be able to add beautiful, elegant haptic experiences to your app that will feel natural and exciting to your users. Because when implemented correctly, haptics can really take any touch to the next level.

Understanding the different types of haptic feedback

Ever since the iPhone 6s, we have been able to add haptics to apps. Haptic feedback is perceived as tiny little nudges or taps that you get from your phone when something happens. It's almost like a very short vibration, except it seems to have a little bit more force to it. The same type of technology that's found in the iPhone 6s and up can be found in the Apple watch and in Apple's trackpads. If you have a MacBook (pro) and you click the trackpad, nothing actually clicks. That's the taptic engine giving you haptic feedback.

The haptics that exist on iOS 12, and in your MacBook's trackpad are fairly limited in what you can with them. With iOS 13 Apple has made a huge change to the haptic capabilities we have. Instead of very simple haptics that can't express much more than that clicking feeling, we can almost make the user feel sound through haptics. This is all enabled through the CoreHaptics framework.

In this framework, we have two key types of haptic feedback, continuous and transient. Transient haptic feedback is like a tap, or a nudge and is typically used when you tap a button, when you need the user's attention or if you want to enrich the visual experience of two objects bumping into each other with some nice haptic feedback. For this kind of haptic feedback, you can configure its intensity to specify how intense the tap should be, and its sharpness. A sharp haptic will feel more urgent to the user, while a less sharp haptic will feel very calm. If this sounds abstract, or strange, I recommend that you come back to this section after playing with haptics a bit. You'll realize that sharp and intense are actually very clever ways to describe what certain haptic feedback feels like.

Continuous haptic feedback is more like a vibration pattern. It still has that typical haptic click feeling, except there are many of them in rapid succession. For a continuous event, you can specify exactly the same parameters that are available for transient feedback, except you can also set the event's duration to determine how long the haptic feedback should keep going for.

It's also possible to define how certain characteristics of a haptic event should change over time. For example, to create an increasing and then decreasing continuous haptic, you would define a single haptic event and apply dynamic parameters or a parameter curve to have the haptic fade in and out over time. I will show an example of both dynamic parameters and parameter curves in the next section.

In addition to tactile feedback, CoreHaptics also has special audio haptic event types. Audio haptic can either be a custom audio file that is played or a generated waveform that is configured similar to a haptic event, except it's played as audio rather than a haptic pulse. You can configure parameters like the audio's pitch, volume and decay to create some very interesting audio effects to accompany your haptic experiences.

Adding haptics to your app

All haptic feedback by CoreHaptics is played through an instance of CHHapticEngine. You can create an instance of the haptic engine as follows:

let engine = try CHHapticEngine()

Note that you should define the haptic engine somewhere where the instance will stay around for as long as you need it. You should avoid creating an engine every time you want to play haptic feedback. Instead, make it a property on the object that will end up managing your haptics implementation so it stays around for a longer time.

You should also set the engine's stoppedHandler and its resetHandler. The stoppedHandler is called in case the haptic engine is stopped due to external factors or if it's finished playing your haptic events. The resetHandler is called if something went wrong and the engine has to be reset. In the reset handler, you will typically want immediately start the engine again.

Before you can play haptic feedback, you must start the haptic engine by calling its start method:

do {
  try engine.start()
} catch {
  // failed to start the engine
}

Once the engine is ready to play haptics, we can define our haptic pattern, ask the haptic engine to create an instance of CHHapticPatternPlayer and then play the haptics. Let's create a simple transient haptic event:

let event = CHHapticEvent(eventType: .hapticTransient, parameters: [
  CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
  CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
], relativeTime: 0)

do {
  let pattern = try CHHapticPattern(events: [event], parameters: [])
  let player = try engine.makePlayer(with: pattern)
  try player?.start(atTime: CHHapticTimeImmediate)
} catch {
  // something went wrong
}

The code above defines a haptic event. Haptic events are the building blocks that can be used to define a haptic pattern. In this case, we have a pattern that only contains a single haptic event. It is possible to, for example, play a transient haptic event first, then play a continuous haptic for a second, and then play another transient haptic event. You can use the relativeTime parameter on the CHHapticEvent initializer to specify an offset for each event. So if you want to play several haptic taps in succession, you would use the relativeTime to make sure each haptic starts when needed rather than playing the entire pattern all at once.

Once the pattern is defined, we can create an instance of CHHapticPattern. The parameters property in the pattern's initializer is an array of CHHapticDynamicParameter items that define how the haptic pattern's intensity, sharpness, and other characteristics should be manipulated over time. I'll show you an example shortly.

Once the pattern is created, we can ask the haptic engine for a player, and we can then play our haptic pattern on the device. You can specify a delay for the player's start time if needed. We use CHHapticTimeImmediate in this example, but you can specify any TimeInterval (or Double) you want. Fairly straightforward, right? In the following examples, I will only show the steps to create the CHHapticPattern. All steps involving obtaining and starting a player are identical for all haptic patterns.

Let's see an example of building a haptic pattern as described earlier where we have a transient event, then a continuous one, and then another transient one:

let events = [
  CHHapticEvent(eventType: .hapticTransient, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
  ], relativeTime: 0),
  CHHapticEvent(eventType: .hapticContinuous, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.7),
  ], relativeTime: 0.1, duration: 0.5),
  CHHapticEvent(eventType: .hapticTransient, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
  ], relativeTime: 0.7)
]

let pattern = try CHHapticPattern(events: events, parameters: [])

Note that the second haptic event, the continuous one, defines its duration while the transient events don't. This is because a transient haptic event is a single event, that doesn't have a duration. If you try running this pattern on a device, you will feel a short tap, then a sharp buzz, followed by another short tap. Cool, right!

Let's see how we can use parameters to create an interesting continuous haptic pattern.

let events = [
  CHHapticEvent(eventType: .hapticContinuous, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
  ], relativeTime: 0.1, duration: 3)
]

let parameters = [
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 0.3, relativeTime: 0),
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 1, relativeTime: 1),
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 0.5, relativeTime: 2),
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 1, relativeTime: 2.5)]

let pattern = try CHHapticPattern(events: events, parameters: parameters)

Note that the value property of a dynamic parameter is used as a multiplier for the value specified on the parameter it manipulates. So in this case setting a value of 1 on the dynamic pattern equals an intensity of 0.8. A value of 0.5 equals an intensity of 0.4, etc.

If you play this you will notice that the changes for the dynamic parameters are very abrupt. After one second we jump to a higher intensity. One second later, the pattern jumps to a lower intensity and so forth. If you want to apply a more streamlined transition, we can use CHHapticParameterCurve instead of CHHapticDynamicParameter. Let's see how:

let events = [
  CHHapticEvent(eventType: .hapticContinuous, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
  ], relativeTime: 0.1, duration: 3)
]

let dynamicParameters = [
  CHHapticParameterCurve(parameterID: .hapticIntensityControl,
                         controlPoints: [.init(relativeTime: 0, value: 0),
                                         .init(relativeTime: 1, value: 1),
                                         .init(relativeTime: 2, value: 0.5),
                                         .init(relativeTime: 3, value: 1)],
                         relativeTime: 0)]

let pattern = try CHHapticPattern(events: events, parameterCurves: dynamicParameters)

Everything looks very similar except we define an array of CHHapticParameterCurve instead of CHHapticDynamicParameter. Note that there is a controlPoints argument on the CHHapticParameterCurve initializer. This is where we specify how we want our pattern to change over time. We create instances of CHHapticParameterCurve.ControlPoint to define how the values change over time. Note that I used .init as a shorthand because the real type name is a bit long to type. If you run this pattern on a device, you should notice that the animations are much smoother.

Before we take a look at using AHAP files for haptic, let's also quickly define a custom audio haptic:

let events = [
CHHapticEvent(eventType: .audioContinuous, parameters: [
  CHHapticEventParameter(parameterID: .audioPitch, value: 0.3),
  CHHapticEventParameter(parameterID: .audioVolume, value: 0.6),
  CHHapticEventParameter(parameterID: .decayTime, value: 0.1),
  CHHapticEventParameter(parameterID: .sustained, value: 0)
], relativeTime: 0)
]

let pattern = try CHHapticPattern(events: events, parameters: [])

The process of creating an audio haptic is very similar to creating a tactile haptic. Try running this on your device and you should hear a short beep-like sound. Combining this with a sharp haptic with medium intensity makes for a very interesting effect. Try playing around with combining audio and tactile haptic to create feedback that delights your users.

Using AHAP files to define your haptics

One last thing I want to cover in this already fairly long Quick Tip is using AHAP files to define your haptic in files. Let's look at a very brief example of what the contents of an AHAP file look like:

{
  "Version": 1.0,
  "Metadata": {
    "Project" : "Sample",
    "Created" : "12-11-2019",
    "Description" : "Quick haptic sample"
  },
  "Pattern": [
    { "Event":
      {
        "Time": 0.0,
        "EventType": "HapticTransient",
        "EventParameters": [
          { "ParameterID": "HapticIntensity", "ParameterValue": 1 },
          { "ParameterID": "HapticSharpness", "ParameterValue": 0.2 }]
      }
    }
  ]
}

You might notice that the pattern itself looks very similar to the patterns we defined in code. The big difference is that we now have a JSON-like file called an AHAP file that we can edit and update outside of our app. We could even fetch these AHAP files from a remote server to dynamically update the haptics in an app. Let's see how you can play an AHAP file in your app:

guard let path = Bundle.main.path(forResource: "myAhapFile", ofType: "ahap") else {
  return
}

try engine.playPattern(from: URL(fileURLWithPath: path))

All that we need to do to play an AHAP file is load it from the bundle, and pass it to the haptic engine. It will then read the file, create the player and play the pattern specified in your file.

In Summary

First, my apologies for making yet another Quick Tip that's way longer than I intended. There are just so many cool things you can do with haptics and I wanted to make sure that you have all the tools needed to get started with haptics in your own apps. If you need some more inspiration, be sure to check out this WWDC video from 2019 it has many beautiful examples of how haptics improve the user experience.

Also, make sure to check out the documentation for the CoreHaptics framework. There is a lot of good information available, and even a couple of sample apps that will help you to explore haptics and hopefully inspire you to integrate awesome haptic experiences in your app.

If you have questions, feedback or suggestions, don't hesitate to reach out on Twitter.

Deciding where to store data

Developers often need to store data on behalf of their users. Sometimes it’s a small amount simple data, other times it’s a lot of complex data. Maybe it’s highly sensitive data or maybe it’s less sensitive or even publicly available data. Choosing where to store this data is often not trivial, especially if you might be unaware of the options that are available to you, or the security considerations you need to keep in mind.

In this week’s blog post I will show you several storage options that are available to you as an iOS developer, and I'll explain the pros and cons of every storage method.

In this post, you will learn about the following storage mechanisms:

  • UserDefaults
  • Files on disk
  • The Keychain
  • Databases (like CoreData and SQLite)

Disclaimer:
Please note that I am not a security or encryption expert. For this reason, I will only cover every storage mechanism in its “normal”, basic usage. I won’t cover how you can best encrypt data to increase security for any of the listed storage mechanisms. In general, you should at least consider any data that you store on a device accessible by attackers that have access to a physical device.

With that disclaimer out of the way, let’s start exploring storage options on iOS, shall we?

Utilizing UserDefaults storage

Possibly the most well-known, easiest to use storage on iOS is the user defaults store. You typically use this storage for simple user preferences or to store simple values that help improve the user’s experience. In my blog post on optimizing your app’s App Store reviews, user defaults are used to track several metrics like the number of launches, when the user first launched the app and more to figure out the best moment to show the user a review prompt.

What should be stored in UserDefaults

You can store a fairly large amount of data in user defaults but it’s not encouraged to store large blobs of data, images and other large data structures in user defaults. On tvOS, the user defaults store is even limited to a maximum 1MB of data and Apple recommends not exceeding 512KB. This amount of storage is a lot of you only store simple data like booleans, numbers or strings because they're small and very well-suited for key-value storage. Anything larger will fill up your store rapidly.

UserDefaults and security

You should never store any sensitive data in user defaults. This storage is not encrypted at all so if an app other than your own obtains access to your user defaults store, your user’s data is compromised.

For example, simple preferences that can’t be used to identify your users are okay to store in user defaults. However, a user’s email address, password and other, similar data, should be stored somewhere more secure.

Using the UserDefaults store

If you wish to use the user defaults store, you do so through the UserDefaults class. You can use the default user store that is always available to your app, or you can create your own user defaults store with its own unique identifier. I always recommend the latter since it makes it somewhat easier to swap your user defaults store out for a different one and it allows you to separate your own user defaults from those that can be accessed by external dependencies that you might include in your app.

Reading from user defaults is really fast because your app's preferences are loaded into memory when your app launches. This means that you don't have to access the underlying disk storage at all to retrieve values using the UserDefaults class.

You can create and use your own user defaults store as follows:

let myDefaults = UserDefaults(suiteName: "com.donnywals.myapp")
myDefaults?.set(true, forKey: "com.donnywals.myapp.user-is-awesome")
let isUserAwesome = myDefaults?.bool(forKey: "com.donnywals.myapp.user-is-awesome")

Note that you might want to use Swift 5’s powerful property wrappers or a wrapper around UserDefaults to make accessing your user defaults a little bit cleaner since it’s easy to make mistakes while typing your user defaults keys. Especially if they’re quite long or complex like they are in the example that you just read.

Storing files on disk

Sometimes you need more than the limited storage user defaults gives you. If this is the case, you might want to look into storing files on the user’s file system. The file system typically has a large amount of storage available, but it can be relatively slow to read large amounts of data from. As with everything, this is a tradeoff and only by measuring and testing will you know whether you need something quicker than disk access, like for instance an SQLite store or in-memory storage.

What should be stored on disk

Typically, you will use disk storage for larger amounts of data. For example images, videos or large JSON files are suited to be stored on disk. These kinds of files are known as binary data and they have no properties that you might want to query in a database for example. Documents that the user creates like text files or PDFs are also typically stored on disk if possible. It's not uncommon for developers to make data structures that they want to store on disk conform to Codable so they can easily convert their objects to Data, which can then be written to a file on the file system. Using disk storage to store your Codable objects is especially nice if you want to create a cache of responses that you receive from the network, or if you just want to make sure certain objects persist across app launches.

Disk storage and security

Without any custom encryption, disk storage is very insecure. Apple encrypts devices when they are locked but as soon as the device is unlocked and your app is active, all bets are off. You should avoid storing sensitive data like usernames, passwords, and more in a file that you write to disk at all costs.

Writing files to disk

Most commonly, you will write files to the documents directory, or if the data is used as a cache and can easily be recreated, the caches directory. The following example shows how to write data to the documents directory:

do {
  let fileManager = FileManager.default
  let docs = try fileManager.url(for: .documentDirectory,
                                 in: .userDomainMask,
                                 appropriateFor: nil, create: false)
  let path = docs.appendingPathComponent("myFile.txt")
  let data = "Hello, world!".data(using: .utf8)!
  fileManager.createFile(atPath: path.absoluteString, contents: data, attributes: nil)
} catch {
  // handle error
}

The preceding code snippet creates a file called myFile.txt in the documents directory with Hello, world! as its contents. You can use any kind of Data as the value of contents. For example, you might want to encode a struct into JSON using a JSONEncoder or grab some image data instead.

Using the keychain to store data

The third storage mechanism to consider is the keychain. This is the place where iOS stores all kinds of sensitive user data like usernames, passwords, certificates and more. If you have secure data that you need to store somewhere, the keychain should be your place to go.

What should be stored in the keychain

The keychain is intended to store your user’s sensitive, secret data. I personally use the keychain to store things like OAuth tokens that authenticate the user against a web service. Apple themselves use the keychain for much more than just tokens, they even store entire security certificates in the keychain.

The keychain and security

On iOS, the keychain is probably the best-secured place for you to store data. However, like everything on a device, the keychain can be cracked and you should keep that in mind. Even though Apple is so confident in the keychain that they use it to store username and password combinations, I’m a little bit too paranoid for that. If I can avoid storing passwords, I will. I’d much rather store a token that has no special meaning to the user than storing (and potentially losing) something as sensitive as their username and password.

Reading and writing to the keychain

The Keychain’s APIs are unfortunately very non-trivial to navigate and use. The good news is that there are wrappers available on CocoaPods, Carthage and Swift Package Manager that make using the Keychain much, much easier. I personally like KeychainAccess by kishikawakatsumi a lot.

Database

The last storage option I’ll cover in this comparison is using a database. Databases are very good at efficiently storing large amounts of structured data. And they’re not just good at storing data, they’re also very good at retrieving and filtering data. So whenever you have a larger set of data that you want to filter, store or expand upon later, a database that's accessed using plain SQLite, or a database wrapper/manager like Realm or CoreData might be ideal for you. I’m not going to compare the performance and usage of every one of these options since in the end, they’re all good at the same thing that all databases are good at.

What should be stored in a database

If you have large objects of structured data like a user’s to-do list or metadata for documents the user creates in your app, data from your user’s weekly running exercises you’re probably going to need a database. If any of these objects have large binary blobs attached to them, like images, you might want to consider mixing disk storage and database storage by writing the large blob of data to disk and storing the path to your data in the database.

Databases and security

Databases on iOS are stored on the device, often in the documents directory. This means that you should consider any data that’s written to your databases compromised unless you’ve applied your own layer of encryption. As always, be very careful with what you store and avoid storing any sensitive data like usernames, passwords, and access token. The Keychain is a better fit for these kinds of data.

Reading and writing to a database

Depending on the database of your choice you will use SQL queries, predicates or something else to access and filter the data in your database. Keep in mind that databases often aren’t thread-safe so you need to be careful when reading and writing data from different threads.

In summary

In this blog post, you’ve learned a lot about different storage options on iOS. You learned that the Keychain is the most secure place to store user secrets. User defaults are great for simple key-value pairs that represent user preferences. File storage is great for storing large blobs of data and lastly, databases should be used to store structured data.

Security-wise, it’s good to reiterate that any data stored on the device should be considered compromised, it’s really just a matter of how complicated it is to extract and decrypt data. Always ask yourself whether you really need to store something that’s user-sensitive.

I hope this blog post will help you to make informed decisions about data storage. If you have questions, doubts, feedback or comments don’t hesitate to reach out to me on Twitter.

Thanks to Ahmed Khalaf for suggesting that I add notes about the max storage for user defaults and that it's loaded into memory when your app launched. And to Vadim Bulavin for pointing out that I omitted that User Defaults storage is only limited to 1MB on tvOS.

Updating your apps with silent push notifications

A lot of apps rely on data from a remote source to display their content. Sometimes the content on the remote source changes regularly, sometimes it changes only sporadically. In all cases, your users will want to see the most up to date version of the information that you have to offer them, and in most cases, you will do your best to make sure that your users always have up to date information.

There are many ways to make sure your users always see the latest data from your server. For example, you might be using web sockets if your data updates with very high frequency. If your data updates less frequently, or if you want to be able to fetch new data on-demand when your app is running in the background, you might want to use silent push notifications, which is exactly what this week’s Quick Tip is all about.

In a nutshell, with silent push notifications, your server can notify your app that it has new data available. Your app will be woken up if it’s in the background and a special AppDelegate method is called allowing you to retrieve and store this new data. Let’s go into a little bit more detail, shall we?

Configuring your app for silent push notifications

To receive silent push notifications you need to add the Push Notifications and Background Modes capabilities to your project. Make sure to check the Remote Notifications checkbox under the Background Modes header as shown in the following screenshot:

Screenshot of background modes enabled

Next, you need to register your application for remote notifications. Place the following code in your AppDelegate’s application(_:didFinishLaunchingWithOptions:) method:

UIApplication.shared.registerForRemoteNotifications()

Note that calling registerForRemoteNotifications() does not trigger the notification permissions popup. It only registers the current device on Apple’s notification servers. Next, implement application(_:didRegisterForRemoteNotificationsWithDeviceToken:) like you would for normal push notifications. Send the device token to a web server, or extract the device token from the data object for debugging using the following code:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  let token = deviceToken.reduce("") { $0 + String(format: "%02.2hhx", $1) }
  print("registered for notifications", token)
}

The last step is to implement application(_:didReceiveRemoteNotification:fetchCompletionHandler:). This method is called when your application receives a remote notification. You will perform your background updates in this method. The following code is a very simple example of fetching data from a server and calling the fetchCompletionHandler:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

  URLSession.shared.dataTask(with: URL(string: "https://donnywals.com")!) { data, response, error in
    print(data)
    completionHandler(.newData)
  }.resume()
}

In your own applications, you will likely make a request to a more relevant URL and store the data that you fetch from that URL in UserDefaults, Core Data, a file on disk or somewhere else.

Note the following line of code completionHandler(.newData). When your app is launched in the background, you need to make sure that you call your completionHandler in a timely manner to avoid getting cut off or penalized if your task takes too long, or never completes. You can expect to have roughly 30 seconds of background execution time when fetching data in response to a silent push notification. If your task failed, call the handler with a .failed argument. If you attempted to fetch data but didn’t receive any, call the handler with .noData so iOS can prioritize future work for your app appropriately.

Now that you know how to prepare your app for silent push notifications, let’s have a brief look at what you need to do on the server to send a silent push notification.

Configuring your server for silent push notifications

Your server-side configuration is very similar to how you’d set it up for normal notifications. You can use an external service like Firebase Cloud Messaging if you want.

What’s most important is that you include the following in your push notification payload:

  • content_available: 1 in your notification payload
  • apns-push-type = background in your notification header
  • apns-priority = 5 in your notification header

I won’t go into the details of how you can build your notification exactly as this will vary per push provider or if you’re doing it manually.

Including content_available: 1 in your payload tells iOS that this is a notification that you sent because there is new content on the server. Without this, your application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method will never be called.

The apns-push-type = background header is only required on watchOS but Apple recommends that you include it in your silent pushes anyway. And lastly, the apns-priority = 5 header tells iOS that it should launch your app in the background when this push is sent. If you don’t include the priority header, your app will not be launched so make sure to include this in your notification payload.

If everything is configured correctly, you now have a powerful tool at your fingertips to make sure your application always has the most up to date data available for your users!

In summary

In this Quick Tip, you saw how you can configure both your app and remote notifications to support silent push notifications or on-demand background refresh. Note that you should not try to abuse this feature. Apple trusts that you will push updates responsibly and in moderation. Abuse of silent push might be penalized, for example, Apple will heavily throttle the number of times it will wake your app up in response to silent push notifications. Apple themselves recommend that you don’t send more than two or three silent pushes per hour.

Setting up silent push notifications isn’t a terribly complex process but it provides your app with the superpower of always being up to date! And again, with great power comes great responsibility…

If you have any questions, comments or feedback for me you can always reach out to me on Twitter

Real time data exchange using web sockets in iOS 13

Apps send and receive data all the time. Some apps mostly read data from the network, others are more focussed on sending data to servers. Depending on your needs, you might need to be able to send data to a server as quickly as possible, or maybe you need to receive new data as soon as it’s available from a server. Every app has different needs and several mechanisms exist to streamline network communication.

In this post week’s blog post, I will focus on one specific use of networking in apps. We’ll look at using web sockets to receive data as soon as it becomes available on the server. We’ll explore some of the available alternatives and how you can use web sockets with URLSession in iOS 13.

Are you excited? I know I am! Let’s get started then.

Receiving updates from a web server

The simplest and possibly most well-known way to receive updates from a server is by making a request to the server with URLSession. While it’s well-known, it’s also quite possibly the least efficient way to get real-time updates. The server only sends your app new data when it asks for new data. This means that your app should either go to the server many times to get data as soon as possible, which means that a lot of requests might come back without new data. Or alternatively, you might make fewer requests which means that it takes longer for the new data to reach your apps. This process of retrieving new data from a server is called polling.

Another way to retrieve new data from a server with URLSession is by implementing silent push notifications in your app. Your server can send a push notification that includes the content-available key in its apns payload to trigger a special AppDelegate method in your app. You can then request new data from the server and know that you will always receive new data. This is really efficient if your app limits the number of push notifications it sends by grouping updates together. If your server processes a lot of changing data, this might mean that your app receives lots of silent push notifications, which results in lots of requests to your server. Batching the notifications that are sent to your app will limit the number of requests made, but it will also introduce a delay between data becoming available on the server and your app knowing about it.

An alternative to polling and silent push notifications is long-polling. When you implement long-polling in an app, you make a request to a web server and leave the connection open for an extended period of time. Much longer than you normally would when making a request for new data. The server won’t respond to your application’s request until it has new data. Once new data is received, you can make a new long-polling request to receive the next bit of data.

Retrieving data through long-polling has the advantage of receiving new data as soon as it’s available. A potential downside of this approach is that your server will have to maintain many open connections. A properly configured server shouldn’t have too much trouble doing this though, so for receiving data long-polling is actually a pretty decent solution. A real downside of long polling is that you can’t send data over the active connection. This means that you’ll need to send a new request every time you want to send data from your app to the server. And again, in a rapidly changing environment, this could mean that your server will receive lots of incoming requests.

What if there was a way to use a single connection that can be kept active and is used to send and receive data at the same time? Well, you’re in luck. That’s exactly what a web socket does! Let’s see how you can use web sockets in your iOS 13+ apps using URLSession.

Using web sockets to send and receive data

In order to understand what we’re looking at in this blog post, let’s take a quick step back and learn about web sockets from a very high level. Once you have a rough overview of how web sockets work, we’ll look at using them in an app.

Understanding web sockets

Web sockets can be considered an open connection between a client and a server. They can send each other messages, typically these messages are short to make sure they’re sent and received as quickly as possible. So while it’s possible to send a complicated JSON payload with several items over a web socket connection, it’s more likely that you’re going to send each item in your payload individually.

It might seem a little bit counter-intuitive to send and receive many smaller payloads rather than a single bigger payload. After all, when you make requests to a server in a normal setting, you want to make sure you don’t make too many requests and favor a single larger request over several small requests, depending on your exact requirements, of course.

When you’re dealing with web sockets, you don’t send requests. You’re sending messages. And since you’re already connected to the server, you don’t incur any extra overhead when you send many small messages to the server. And since your ultimate goal when using a web socket is often communication with as little latency as possible, it makes sense to send any messages you have for the server as soon as possible in a package that is as small as possible so it can be sent over the network quickly.

The same is true for server-to-app communication. A server that supports web sockets will often prefer to send your app small messages with data as soon as the data becomes available rather than batching up several messages and sending them all at once.

So when we implement web sockets in an app, we need to be prepared to:

  • Send messages as soon as we can and keep them as small as possible.
  • Receive many messages in rapid succession.

Based on these two points, you should be able to decide whether web sockets are a good fit for the task you’re trying to accomplish. For example, uploading a large high-resolution image is not typically a good task for a web socket. The image data can be quite large and the upload might take a while. You’re probably better off using a regular data task for this.

If your app has to sync a large amount of data all at once, you might also want to consider using a separate request to your server to make sure your socket remains available for the quick and short messaging it was designed to handle. Of course, the final decision depends on your use case and trial and error might be key to finding the optimal balance between using web sockets and using regular URL requests in your app.

Now that you know a bit more about web sockets, let’s find out how they work in an iOS application, shall we?

Using web sockets in your app

Using web sockets in your app is essentially a three-step process:

  1. Connecting to a web socket
  2. Send messages over a web socket connection
  3. Receiving incoming messages

Let’s go over each step individually to implement a very simple connection that will send and receive simple messages as either JSON data or a string.

Connecting to a web socket

Establishing a connection to a web socket is done similar to how you make a regular request to a server. You make use of URLSession, you need a URL and you create a task that you must resume. When you make a regular request, the task you need is usually a dataTask. For web sockets, you need a webSocketTask. It’s also important that you keep a reference to the web socket task around so you can send messages using that same task at a later moment. The following code shows a simple example of this:

var socketConnection:  URLSessionWebSocketTask?

func connectToSocket() {
  let url = URL(string: "ws://127.0.0.1:9001")!
  socketConnection = URLSession.shared.webSocketTask(with: url)
  socketConnection?.resume()
}

Note that the URL that’s used for the socket connection is prefixed with ws://, this is the protocol that’s used to connect to a web socket. Similar to how http:// and https:// are protocols to connect to a web server when making a regular request.

After obtaining the web socket task, it must be resumed to actually connect to the web socket server.

Next up, sending a message over the web socket connection.

Sending message over a web socket connection

Depending on your app and the kind of data you wish to send over the web socket, you might want to send strings or plain data objects. Both are available on the Message enum that is defined on URLSessionWebSocketTask as a case with an associated value. Let’s look at sending a string first:

func sendStringMessage() {
  let message = URLSessionWebSocketTask.Message.string("Hello!")

  socketConnection?.send(message) { error in
    if let error = error {
      // handle the error
      print(error)
    }
  }
}

The preceding code creates a message using the string version of URLSessionWebSocketTask.Message. The existing socket connection is then used to send the message. The send(_:) method on URLSessionWebSocketTask takes a closure that is called when the message is sent. The error is an optional argument for the closure that will be nil if the message was sent successfully.

Let’s see how sending data over the socket is different from sending a string:

func sendDataMessage() {
  do {
    let encoder = JSONEncoder()
    let data = try encoder.encode(anEncodableObject)
    let message = URLSessionWebSocketTask.Message.data(data)

    socketConnection?.send(message) { error in
      if let error = error {
        // handle the error
        print(error)
      }
    }
  } catch {
    // handle the error
    print(error)
  }
}

The preceding code uses a JSONEncoder to encode an Encodable object into a Data object and then it creates a message using the data case of URLSessionWebSocketTask.Message. The rest of the code is identical to the version you saw earlier. The message is sent using the existing socket connection and the completion closure is called once the message has been delivered or if an error occurred.

This is pretty straightforward, right? Nothing fancy has to be done to send messages. We just need to wrap our message in a URLSessionWebSocketTask.Message and send it using an existing web socket connection.

Let’s see if receiving messages is just as nice as sending messages is.

Receiving incoming messages

Any time our server has new data that we’re interested in, we want to receive this new data over the web socket connection. To do this, you must provide the web socket task with a closure that it can call whenever incoming data is received.

The web socket connection will call your receive closure with a Result object. The Result’s success type is URLSessionWebSocketTask.Message and the Failure type is Error.

This means that we can either get a string or data depending on the contents of the message. The following code shows how you can set up the receive closure, and how you can distinguish between the two different types of messages you can receive.

func setReceiveHandler() {
  socketConnection?.receive { result in
    defer { self.setReceiveHandler() }

    do {
      let message = try result.get()
      switch message {
      case let .string(string):
        print(string)
      case let .data(data):
        print(data)
      @unknown default:
        print("unkown message received")
      }
    } catch {
      // handle the error
      print(error)
    }
  }
}

Similar to sending messages, receiving them isn’t terribly complex. Since the receive closure can receive either a string or data, we must make sure that the web socket server only sends us responses we expect. It’s important to coordinate with your server team, or yourself, that you can handle unexpected messages and that you only send strings or data in a pre-defined format.

Note the defer statement at the start of the receive handler. It calls self.setReceiveHandler() to reset the receive handler on the socket connection to allow it to receive the next message. Currently, the receive handler you set on a socket connection is only called once, rather than every time a message is received. By using a defer statement, you make sure that self.setReceiveHandler is always called before exiting the scope of the receive handler, which makes sure that you always receive the next message from your socket connection.

If you want to use a JSONDecoder to decode that data you’ve received from the server, you need to either attempt to decode several different types of objects until an attempt succeeds, or you need to make sure that you can only receive a single type of data over a certain web socket connection.

Personally, I would recommend to always make sure that you send a single type of message over a socket connection. This allows you to write robust code that is easy to reason about.

For example, if you’re building a chat application, you’ll want to make sure that your web socket only sends and receives instances of a ChatMessage that conforms to Codable. You might even want to have separate sockets for each active chat a user has. Or if you’re building a stocks application, your web sockets will probably only receive StockQuote objects that contain a stock name, timestamp, and a current price.

If you make sure that your messages are well-defined, URLSession will make sure that you have a convenient way of using your web socket.

In Summary

Web sockets are an amazing technology that, as you now know, allow you to communicate with a server in real-time, using a single active connection that can be used to both send and receive messages. You know that the big advantage here is that there is no overhead of making requests to a server every time you want to either send or receive data.

You also saw how you can use URLSession’s web socket task to connect to a web socket, send messages and receive them by using very straightforward and convenient APIs. Lastly, you learned that you must pre-define the messages you want to send and receive over your web socket to make sure all components involved understand what’s going on.

I personally really like web sockets, especially because you can use them to build experiences that feel like magic. I once used web sockets to build an app that could be controlled through a companion website by sending messages over a web socket, it was really cool to see the app reflect the things I was doing on the website.

If you have any questions about this blog post, have feedback or anything else, don’t hesitate to reach out to me on Twitter!

Thanks to Kristaps Grinbergs for pointing out that receive handlers are always called once 🙌🏼.