How to have more than one type of cell in a Collection View

Collection views in iOS are awesome. You can use them to build complex custom layouts and since iOS 13 you can use Compositional Layouts to quickly build good looking layouts that would take forever to accomplish on iOS 12 and below.

But what if you want to use more than one type of cell in your layout?

If you're building your app without storyboard you register collection view cells using the register method on UICollectionView. If you want to use more than one cell type, all you need to do is call register multiple times:

collectionView.register(CellTypeOne.self, forCellWithReuseIdentifier: "CellTypeOne")
collectionView.register(CellTypeTwo.self, forCellWithReuseIdentifier: "CellTypeTwo")

Each cell type has its own class and its own reuse identifier. In the collection view's cellForItemAt delegate method you can return either of your registered cells like this:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  if indexPath.section == 0 {
    let dequeuedCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellTypeOne", for: indexPath)

    guard let cellOne = dequeuedCell as? CellTypeOne else {
      fatalError("Wrong cell type for section 0. Expected CellTypeOne")
    }

    // configure your CellTypeOne

    return cellOne
  } else {
    let dequeuedCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellTypeTwo", for: indexPath)

    guard let cellTwo = dequeuedCell as? CellTypeTwo else {
      fatalError("Wrong cell type for section 0. Expected CellTypeTwo")
    }

    // configure your CellTypeTwo

    return cellTwo
  }
}

In my example, I want to use CellTypeOne for the first section in my collection view and CellTypeTwo for all other sections. You can use any logic you want to determine which cell should be used. Notice that using multiple cell types ultimately isn't too different from using one cell type. The only difference is that you'll have an if somewhere to determine which cell type you want to dequeue.

If you're using Storyboards to build your app the process of dequeuing cells is the same as it is when you're doing everything in code. The only difference is how you register the cells on your collection view. It's perfectly fine to mix Storyboards and programmatic so you could use register to register new cells on the collection view that you created in a Storyboard.

Alternatively, you can drag more than one cell from the Object Library into your collection view, assign a subclass and reuse identifier for the cell and design it as needed. Just like you would for a single cell. Dragging multiple cells into your collection view in a Storyboard automatically registers these cells on your collection view. You can use and dequeue them just like I showed you in the code above.

If you have any questions about this tip, or if you have feedback for me, don't hesitate to reach out on Twitter.

What is type erasure in Swift? An explanation with code samples

Swift's type system is (mostly) fantastic. Its tight constraints and flexible generics allow developers to express complicated concepts in an extremely safe manner because the Swift compiler will detect and flag any inconsistencies within the types in your program.

While this is great most of the time, there are times where Swift's strict typing gets in the way of what we're trying to build. This is especially true if you're working on code that involves protocols and generics.

With protocols and generics, you can express ideas that are insanely complex and flexible. But sometimes you're coding along happily and the Swift compiler starts yelling at you. You've hit one of those scenarios where your code is so flexible and dynamic that Swift isn't having it.

Let's say you want to write a function that returns an object that conforms to a protocol that has an associated type? Not going to happen unless you use an opaque result type.

But what if you don't want to return the exact same concrete type from your function all the time? Unfortunately, opaque result types won't help you there. Luckily, Swift 5.7 which came out in 2022 allows us to define so-called primary assocated types which allow us to specialize our opaque return types where needed.

It's important to note that primary associated types remove many of the reasons the use type erasure in your app, but they don't make type erasure completely obsolete.

So when the Swift compiler keeps yelling at you and you have no idea how to make it stop, it might be time to apply some type erasure.

In this week's blog post I will explain what type erasure is and show an example of how type erasure can be used to craft highly flexible code that the Swift compiler will be happy to compile.

There are multiple scenarios where type erasure makes sense and I want to cover two of them.

Using type erasure to hide implementation details

The most straightforward way to think of type erasure is to consider it a way to hide an object's "real" type. Some examples that come to mind immediately are Combine's AnyCancellable and AnyPublisher. An AnyPublisher in Combine is generic over an Output and a Failure. If you're not familiar with Combine, you can read up in the Combine category on this blog. All you really need to know about AnyPublisher is that it conforms to the Publisher protocol and wraps another publisher. Combine comes with tons of built-in publishers like Publishers.Map, Publishers.FlatMap, Future, Publishers.Filter, and many, many more.

Often when you're working with Combine, you will write functions that set up a chain of publishers. You usually don't want to expose the publishers you used to callers of your function. In essence, all you want to expose is that you're creating a publisher that emits values of a certain type (Output) or fails with a specific error (Failure). So instead of writing this:

func fetchData() -> URLSession.DataTaskPublisher<(data: Data, response: URLResponse), URLError> {
  return URLSession.shared.dataTaskPublisher(for: someURL)
}

You will usually want to write this:

func fetchData() -> AnyPublisher<(data: Data, response: URLResponse), URLError> {
  return URLSession.shared.dataTaskPublisher(for: someURL)
    .eraseToAnyPublisher()
}

By applying type erasure to the publisher created in fetchData we are now free to change its implementation as needed, and callers of fetchData don't need to care about the exact publisher that's used under the hood.

When you think about how you can refactor this code, you might be tempted to try and use a protocol instead of an AnyPublisher. And you'd be right to wonder why we wouldn't.

Since a Publisher has an Output and Failure that we want to be able to use, using some Publisher wouldn't work. We wouldn't be able to return Publisher due to its associated type constraints, so returning some Publisher would allow the code to compile but it would be pretty useless:

func fetchData() -> some Publisher {
  return URLSession.shared.dataTaskPublisher(for: someURL)
}

fetchData().sink(receiveCompletion: { completion in
  print(completion)
}, receiveValue: { output in
  print(output.data) // Value of type '(some Publisher).Output' has no member 'data'
})

Because some Publisher hides the true type of the generics used by Publisher, there is no way to do anything useful with the output or completion in this example. An AnyPublisher hides the underlying type just like some Publisher does, except you can still define what the Output and Failure types are for the publisher by writing AnyPublisher<Output, Failure>.

With primary associated types in Swift 5.7, you can write the following code:

func fetchData() -> any Publisher<(Data, URLResponse), URLError> {
  return URLSession.shared.dataTaskPublisher(for: someURL)
}

The only problem with using primary associated types on a Publisher is that not all methods that exist on publishers like an AnyPublisher are added to the Publisher protocol. This means that we might lose some functionality by not using AnyPublisher.

I will show you how type erasure works in the next section. But first I want to show you a slightly different application of type erasure from the Combine framework. In Combine, you'll find an object called AnyCancellable. If you use Combine, you will encounter AnyCancellable when you subscribe to a publisher using one of Combine's built-in subscription methods.

Without going into too much detail, Combine has a protocol called Cancellable. This protocol requires that conforming objects implement a cancel method that can be called to cancel a subscription to a publisher's output. Combine provides three objects that conform to Cancellable:

  1. AnyCancellable
  2. Subscribers.Assign
  3. Subscribers.Sink

The Assign and Sink subscribers match up with two of Publisher's methods:

  1. assign(to:on:)
  2. sink(receiveCompletion:receiveValue)

These two methods both return AnyCancellable instances rather than Subscribers.Assign and Subscribers.Sink. Apple could have chosen to make both of these methods return Cancellable instead of AnyCancellable.

But they didn't.

The reason Apple applies type erasure in this example is that they don't want users of assign(to:on:) and sink(receiveCompletion:receiveValue) to know which type is returned exactly. It simply doesn't matter. All you need to know is that it's an AnyCancellable. Not just that it's Cancellable, but that it could be _any MARKDOWN_HASH9fba8e737f748904c9dc7415d4876e4aMARKDOWN<em>HASH.

Because AnyCancellable erases the type of the original Cancellable by wrapping it, you don't know if the AnyCancellable wraps a Subscribers.Sink or some other kind of internal, private Cancellable that we're not supposed to know about.

If you have a need to hide implementation details in your code, or if you run into a case where you want to return an object that conforms to a protocol that has an associated type that you need to access without returning the actual type of object you wanted to return, type erasure just might be what you're looking for.

Applying type erasure in your codebase

To apply type erasure to an object, you need to define a wrapper. Let's look at an example:

protocol DataStore {
  associatedtype StoredType

  func store(_ object: StoredType, forKey: String)
  func fetchObject(forKey key: String) -> StoredType?
}

class AnyDataStore<StoredType>: DataStore {
  private let storeObject: (StoredType, String) -> Void
  private let fetchObject: (String) -> StoredType?

  init<Store: DataStore>(wrappedStore: Store) where Store.StoredType == StoredType {
    self.storeObject = wrappedStore.store
    self.fetchObject = wrappedStore.fetchObject
  }

  func store(_ object: StoredType, forKey key: String) {
    storeObject(object, key)
  }

  func fetchObject(forKey key: String) -> StoredType? {
    return fetchObject(key)
  }
}

This example defines a DataStore protocol and a type erasing wrapper called AnyDataStore. The purpose of the AnyDataStore is to provide an abstraction that hides the underlying data store entirely. Much like Combine's AnyPublisher. The AnyDataStore object makes extensive use of generics and if you're not too familiar with them this object probably looks a little bit confusing.

The AnyDataStore itself is generic over StoredType. This is the type of object that the underlying DataStore stores. The initializer for AnyDataStore is generic over Store where Store conforms to DataStore and the objects that are stored in the Store must match the objects stored by the AnyDataStore. Due to the way this wrapper is set up that should always be the case but Swift requires us to be explicit.

We want to forward any calls on AnyDataStore to the wrapped store, but we can't hold on to the wrapped store since that would require making AnyDataStore generic over the underlying data store, which would expose the underlying datastore. Instead, we capture references to the method we need in the storeObject and fetchObject properties and forward any calls to store(_:forKey:) and fetchObject(forKey:) to their respective stored references.

It's quite a generics feast and again, if you're not too familiar with them this can look confusing. I wrote about generics a while ago so make sure to click through to that post if you want to learn more.

Let's see how this AnyDataStore can be used in an example:

class InMemoryImageStore: DataStore {
  var images = [String: UIImage]()

  func store(_ object: UIImage, forKey key: String) {
    images[key] = object
  }

  func fetchObject(forKey key: String) -> UIImage? {
    return images[key]
  }
}

struct FileManagerImageStore: DataStore {
  typealias StoredType = UIImage

  func store(_ object: UIImage, forKey key: String) {
    // write image to file system
  }

  func fetchObject(forKey key: String) -> UIImage? {
    return nil // grab image from file system
  }
}

class StorageManager {
  func preferredImageStore() -> AnyDataStore<UIImage> {
    if Bool.random() {
      let fileManagerStore = FileManagerImageStore()
      return AnyDataStore(wrappedStore: fileManagerStore)
    } else {
      let memoryStore = InMemoryImageStore()
      return AnyDataStore(wrappedStore: memoryStore)
    }
  }
}

In the code snippet above I create two different data stores and a StorageManager that is responsible for providing a preferred storage solution. Since the StorageManager decides which storage we want to use it returns an AnyDataStore that's generic over UIImage. So when you call preferredImageStore() all you know is that you'll receive an object that conforms to DataStore and provides UIImage object.

Of course, the StorageManager I wrote is pretty terrible. When you're working with data and storing it you need a lot more control over what happens and whether data is persisted. And more importantly, a StorageManager that will randomly switch between stores is not that useful. However, the important part here is not whether or not my DataStore is good. It's that you can use type erasure to hide what's happening under the hood while making your code more flexible in the process.

The example of AnyDataStore I just showed you is very similar to the AnyPublisher scenario that I described in the previous section. It's pretty complex but I think it's good to know this exists and how it (possibly) looks under the hood.

In the previous section, I also mentioned AnyCancellable. An object like that is much simpler to recreate because it doesn't involve any generics or associated types. Let's try to create something similar except my version will be called AnyPersistable:

protocol Persistable {
  func persist()
}

class AnyPersistable: Persistable {
  private let wrapped: Persistable

  init(wrapped: Persistable) {
    self.wrapped = wrapped
  }

  func persist() {
    wrapped.persist()
  }
}

An abstraction like the one I showed could be useful if you're dealing with a whole bunch of objects that need to be persisted but you want to hide what these objects really are. Since there are no complicated generics involved in this example it's okay to hold on to the Persistable object that's wrapped by AnyPersistable.

In summary

In this post, you learned about type erasure. I showed you what type erasing is, and why it's used. You saw how Apple's Combine framework uses type erasure to abstract Publisher and Cancellable objects and hide their implementation details. This can be really useful, especially if you're working on a framework or library where you don't want others to know which objects you are using internally to prevent users from making any assumptions about how your API works internally.

After explaining how type erasure is used, I showed you two examples. First, you saw a complicated example that uses generics and stores references to functions as closures. It's pretty complex if you haven't seen anything like it before so don't feel bad if it looks a little crazy to you. I know that with time and experience, a construction like the one I showed you will start to make more sense. Type erasure can be a pretty complicated topic.

The second example I showed you was simpler because it doesn't involve any generics. It mimics what Apple does with Combine's AnyCancellable to hide the underlying Cancellable objects from developers.

If you have any questions about this post or if you have feedback for me, reach out to me on Twitter

Getting started with testing your Combine code

A question that often comes up when folks get into learning Combine is "how do I test code that uses Combine?". In this week's post, I will briefly explain the basics of testing Combine code. I will assume that you already know the basics of testing and Combine. If you're just getting started with both topics or would like a refresher I can recommend that you take a look at the following resources:

By the end of this post you will understand the essentials of testing code that uses Combine.

Effectively writing tests for code that uses Combine

If you know how you can test asynchronous code, you know how to test Combine code. That's the short answer I like to give to people who ask me about testing their Combine code. The essence of testing asynchronous code is that you use XCTest's XCTestExpectation objects to make sure your test sits idle until your asynchronous code has produced a result. You can then assert than this result is the result that you expected and your test will have succeeded (or failed). This same idea applies to Combine code.

Before you write your tests you will need to apply the same abstractions that you would apply for other code, and you'll want to make sure that you can mock or stub any dependencies from the object you're testing. The major difference between testing code that uses Combine, and code that runs asynchronously is that your asynchronous code will typically produce a single result. Combine code can publish a stream of values which means you'll want to make sure that your publisher has published all of the values you expected.

Let's look at a simple model and view model that we can write a test for:

public class Car {
  @Published public var kwhInBattery = 50.0
  let kwhPerKilometer = 0.14
}

public struct CarViewModel {
  var car: Car

  public lazy var batterySubject: AnyPublisher<String?, Never> = {
    return car.$kwhInBattery.map({ newCharge in
      return "The car now has \(newCharge)kwh in its battery"
    }).eraseToAnyPublisher()
  }()

  public mutating func drive(kilometers: Double) {
    let kwhNeeded = kilometers * car.kwhPerKilometer

    assert(kwhNeeded <= car.kwhInBattery, "Can't make trip, not enough charge in battery")

    car.kwhInBattery -= kwhNeeded
  }
}

The CarViewModel in this example provides batterySubject publisher and we'll test that it publishes a new value every time we call drive(kilometers:). The drive(kilometers:) method is used to update the Car's kwhInBattery which means that $kwhInBattery should emit a new value, which is then transformed and the transformed value is them emitted by batterySubject.

Keep in mind that we have no business testing whether the map that's used for the batterySubject works properly. We shouldn't care what Combine operators are used under the hood, and we also should test that Combine's built-in operators work properly. That's Apple's job. Your job is to test the code you write and own.

To test CarViewModel you could write the following unit test:

class CarViewModelTest: XCTestCase {
  var car: Car!
  var carViewModel: CarViewModel!
  var cancellables: Set<AnyCancellable>!

  override func setUp() {
    car = Car()
    carViewModel = CarViewModel(car: car)
    cancellables = []
  }

  func testCarViewModelEmitsCorrectStrings() {
    // determine what kwhInBattery would be after driving 10km
    let newValue: Double = car.kwhInBattery - car.kwhPerKilometer * 10

    // configure an array of expected output
    var expectedValues = [car.kwhInBattery, newValue].map { doubleValue in
      return "The car now has \(doubleValue)kwh in its battery"
    }

    // expectation to be fulfilled when we've received all expected values
    let receivedAllValues = expectation(description: "all values received")

    // subscribe to the batterySubject to run the test
    carViewModel.batterySubject.sink(receiveValue: { value in
      guard  let expectedValue = expectedValues.first else {
        XCTFail("Received more values than expected.")
        return
      }

      guard expectedValue == value else {
        XCTFail("Expected received value \(value) to match first expected value \(expectedValue)")
        return
      }

      // remove the first value from the expected values because we no longer need it
      expectedValues = Array(expectedValues.dropFirst())

      if expectedValues.isEmpty {
        // the  test is completed when we've received all expected values
        receivedAllValues.fulfill()
      }
    }).store(in: &cancellables)

    // call drive to trigger a second value
    carViewModel.drive(kilometers: 10)

    // wait for receivedAllValues to be fulfilled
    waitForExpectations(timeout: 1, handler: nil)
  }
}

I have added several comments to the unit test code. I am testing my Combine code by comparing an array of expected values to the values that are emitted by batterySubject. The first element in the expectedValues array is always the element that I expect to receive from batterySubject. After receiving a value, I use dropFirst() to create a new expectedValues array with all elements from the old expectedValues array, except for the first value. I drop the first value because I just received that value.

In essence, this code isn't too different from any other asynchronous test code you may have written. The most important difference is in the fact that you now need to compare an array of expected values to the values that were actually emitted by your publisher.

Note that I am not using any Combine operators other than sink because I need to subscribe to the publisher I want to test. Using a Combine operator in your test is often a sign that you're not really testing the logic that you should be testing.

If you consider this test to be a little bit wordy, or if you think writing your tests like I just showed you is a bit repetitive when you're testing a whole bunch of publishers, I agree with you. In my Practical Combine book I demonstrate how you can improve a test like this using a simple extension on Publisher that allows you to compare a publisher's output to an array of expected values using just a few lines of code. You will also learn how you can test a publisher that needs to explicitly complete or throw an error before you consider it to be completed, including a nifty helper that allows you to write your tests in a way that almost makes them look like your logic isn't asynchronously tested at all.

In summary

In this week's post, I gave you some insight into testing code that uses Combine. You learned how you can set up an array of expected values, compare them with the values that are emitted by a publisher, and ultimately complete your test when all expected values have been emitted. If you want to learn more about Combine, testing or both make sure to take a look at the testing and Combine categories on this blog. And if you want to gain some deeper knowledge about testing your Combine code make sure to pick up my book on Combine. It includes an entire chapter dedicated to testing with several examples and two convenient helpers to clean up your test code.

Creating type-safe identifiers for your Codable models

Note:
After publishing this article, it has been brought to my attention that the folks from @pointfreeco have a very similar solution for the problems I outline in this post. It's called tagged and implements the same features I cover in this post with several useful extensions. If you like this post and plan to use the concepts I describe, you should take a look at tagged.

It seems that on the Swift forums, there are a couple of topics that come up regularly. One of these topics is the newtype for Swift discussion. Last week, I saw a new topic come up for newtype and I realized that I used to wonder why folks wanted that features and I was opposed to it, and now I'm actually sort of in favor of it.

Newtype, in short, is a feature that allows you to not just create a typealias, but actually allows you to clone or copy a type and create a whole new type in the process. You can almost think of it as subclassing for structs, but it's not quite that. If you want to understand newtype in detail, I can recommend that you take a look at the Swift forum topic. The use cases for a feature like newtype are explained decently over there.

In this post, my goal is not to talk about newtype and convince why we need it. Instead, I wanted to write about one of the problems that are solved by newtype and a solution I came up with that you can take and use in your projects without having a newtype object. By the end of this post, you will understand exactly why you'd want to have type-safe identifiers for your models, what I mean when I say type-safe identifiers and how you can implement them on your models, even if they conform to Codable without having to make huge changes to your codebase.

Understanding why you'd want to have type-safe identifiers

When you're working with models or identifiers, it's not uncommon to have models that use Int or String as their unique identifiers.

For example, here's an example of three models that I prepared for this post:

struct Artist: Decodable {
  let id: String
  let name: String
  let recordIds: [String]
}

struct Record: Decodable {
  let id: String
  let name: String
  let artistId: String
  let songIds: [String]
}

struct Song: Decodable {
  let id: String
  let name: String
  let recordId: String
  let artistId: String
}

These models represent a relationship between artists, records and songs. These models are nothing special. They use String for their unique identifier and they refer to each other by their identifiers.

When you write an API to find the objects represented by these models, that API would probably look a bit like this:

struct MusicApi {
  func findArtistById(_ id: String) -> Artist? {
    // lookup code
  }

  func findRecordById(_ id: String) -> Record? {
    // lookup code
  }

  func findSongById(_ id: String) -> Song? {
    // lookup code
  }
}

I'm sure you're still with me at this point. If you've ever written an API that looks items up by their identifier this code should look extremely familiar. If a match is found, return the found object and otherwise return nil.

What's interesting here is that it's easy to make mistakes. For example, consider the following code:

let api = MusicApi()
let song = Song(id: "song-1", name: "A song", recordId: "record-1", artistId: "artist-1")

api.findRecordById(song.artistId) // nil

Can you see what's wrong in this code?

We're trying to find a record using song.artistId and the compiler is fine with that. After all, artistId, recordId and even id are all properties on Song and they are all instances of String. In fact, there's nothing stopping us from using a bogus string as an input for findRecordById.

Preventing mistakes like the one I just showed is the entire purpose of having type-safe identifiers. If you can get the Swift compiler to tell you that you're using an artist identifier instead of a record identifier, or that you're using a plain string directly instead of an identifier that you created explicitly, your code will be much safer and more predictable in the long run which is fantastic.

So how do we achieve this?

Implementing type-safe identifiers

Let's look at the refactored MusicApi struct before I show you how I implemented my type-safe identifiers. Once you've seen the refactored MusicApi I think you'll have a much better understanding of what it is I was trying to achieve exactly:

struct MusicApi {
  func findArtistById(_ id: Artist.Identifier) -> Artist? {
    // lookup code
  }

  func findRecordById(_ id: Record.Identifier) -> Record? {
    // lookup code
  }

  func findSongById(_ id: Song.Identifier) -> Song? {
    // lookup code
  }
}

Instead of accepting String for each of these method, I expect a specifc kind of object. Because of this, it should be impossible to write the following incorrect code:

let api = MusicApi()
let song = Song(id: "song-1", name: "A song", recordId: "record-1", artistId: "artist-1")

api.findRecordById(song.artistId) // error: cannot convert value of type 'Artist.Identifier' to expected argument type 'Record.Identifier'

The compiler simply won't allow me to do this because the type of artistId on Song is not a Record.Identifier. To show you how this Identifier type works, I want to show you an updated version of the Artist model since that's the simplest model to change. Here's what the old model looked like:

struct Artist: Decodable {
  let id: String
  let name: String
  let recordIds: [String]
}

Very straightforward, right? Now let's look at the updated Artist model and its nested Artist.Identifier:

struct Artist: Decodable {
  struct Identifier: Decodable {
    let wrappedValue: String

    init(_ wrappedValue: String) {
      self.wrappedValue = wrappedValue
    }

    init(from decoder: Decoder) throws {
      let container = try decoder.singleValueContainer()
      self.wrappedValue = try container.decode(String.self)
    }
  }

  let id: Artist.Identifier
  let name: String
  let recordIds: [Record.Identifier]
}

The Identifier struct is nested under Artist which means its full type is Artist.Identifier. This type wraps a string, and it's Decodable. I have defined two initializers on Identifier. One that takes a wrapped string directly, allowing you to create instances of Identifier with any string you choose, and I have defined a custom init(from:) to implement Decodable manually for this object. Note that I use a single value container to extract the string I want to wrap in this Identifier. I won't go into all the fine details of custom JSON decoding right now but consider the following JSON data:

{
  "id": "b0a16f6e-1bf9-4007-807d-d1a59b399a64",
  "name": "Johnny Cash",
  "recordIds": ["9431bcb0-f83b-4eeb-8932-dd105584ca29"]
}

The data represents an Artist object and it has an id property which is a String. This means that when a JSONDecoder tries to decode the Artist object's id property on the original model, it could simply decode the id into a Swift String. Because I changed the type of id from String to Identifier, we need to do a little bit of work to convert "b0a16f6e-1bf9-4007-807d-d1a59b399a64" (which is a String) to Identifier. The Decoder that is passed to the Identifier's custom init(from:) initializer only holds a single value which means we can extract this single value, decode it as a string and assign it to self.wrappedValue.

By implementing Identifier like this, we don't need to do any additional work, the JSON can remain as it was before, we don't need custom decoding logic on Artist.Identifier and only need to extract a single value in the Artist.Identifier custom decoder. Notice that I changed the array of recordIds from [String] to [Record.Identifier]. Let's look at the refactored implementations for Record and Song:

struct Record: Decodable {
  struct Identifier: Decodable {
    let wrappedValue: String

    init(_ wrappedValue: String) {
      self.wrappedValue = wrappedValue
    }

    init(from decoder: Decoder) throws {
      let container = try decoder.singleValueContainer()
      self.wrappedValue = try container.decode(String.self)
    }
  }

  let id: Record.Identifier
  let name: String
  let artistId: Artist.Identifier
  let songIds: [Song.Identifier]
}

struct Song: Decodable {
  struct Identifier: Decodable {
    let wrappedValue: String

    init(_ wrappedValue: String) {
      self.wrappedValue = wrappedValue
    }

    init(from decoder: Decoder) throws {
      let container = try decoder.singleValueContainer()
      self.wrappedValue = try container.decode(String.self)
    }
  }

  let id: Song.Identifier
  let name: String
  let recordId: Record.Identifier
  let artistId: Artist.Identifier
}

Both objects implement the same Identifier logic that was added to Artist which means that we can update all String identifiers to their respective Identifier objects. The implementation for each Identifier is the same every time which makes this code pretty repetitive. Let's refactor the models one last time to remove the duplicated Identifier logic:

struct Identifier<T, KeyType: Decodable>: Decodable {
  let wrappedValue: KeyType

  init(_ wrappedValue: KeyType) {
    self.wrappedValue = wrappedValue
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    self.wrappedValue = try container.decode(KeyType.self)
  }
}

struct Artist: Decodable {
  typealias IdentifierType = Identifier<Artist, String>

  let id: IdentifierType
  let name: String
  let recordIds: [Record.IdentifierType]
}

struct Record: Decodable {
  typealias IdentifierType = Identifier<Record, String>

  let id: IdentifierType
  let name: String
  let artistId: Artist.IdentifierType
  let songIds: [Song.IdentifierType]
}

struct Song: Decodable {
  typealias IdentifierType = Identifier<Song, String>

  let id: IdentifierType
  let name: String
  let recordId: Record.IdentifierType
  let artistId: Artist.IdentifierType
}

Instead of giving each model its own Identifier I have created an Identifier struct that is generic over T which represents the type it belongs to and KeyType which is the type of value that the Identifier wraps. In my example I'm only wrapping String identifiers but making the Identifier struct generic allows me to also use Int, UUID, or other types as identifiers.

Each model now defines a typealias to make working with its identifier a bit easier.

With this update in place, we should also update the MusicApi object one last time:

struct MusicApi {
  func findArtistById(_ id: Artist.IdentifierType) -> Artist? {
    // lookup logic
  }

  func findRecordById(_ id: Record.IdentifierType) -> Record? {
    // lookup logic
  }

  func findSongById(_ id: Song.IdentifierType) -> Song? {
    // lookup logic
  }
}

With this code in place, it's now impossible to accidentally use a bad identifier to search for an object. This means that when we want to look up an Artist, we need to obtain or create an instance of Artist.IdentifierType which is a typealias for Identifier<Artist, String>. Pretty cool, right!

And even though we added a whole new object to the models, it still decodes the same JSON that it did when we started out with String identifiers.

In summary

In this week's post, I offered you a glimpse into what happens when I notice something on the Swift forums that I want to learn more about. In this case, it was a discussion about a potential newtype declaration in Swift. In this post I explored the problem that could be solved by a newtype which is the ability to create a copy of a certain type to make it clear that that copy should not be treated the same as the original type.

I demonstrated this by showing you how a String identifier can be error-prone if somebody accidentally uses the wrong kind of identifier to search for an object in a database. The Swift compiler can't tell you that you're using the wrong identifier because all String instances are equal to the compiler. Even when you hide them behind a typealias.

To work around this problem and help the Swift compiler I showed you how you can create a new struct that wraps an identifier and adds type safety to the identifiers for my models. I made this new Identifier object generic so it's very flexible and because it implements custom decoding logic it can decode a JSON string on its own which means that we can still decode the same JSON that the original String based model could with the added benefit of being type-safe.

Why your @Atomic property wrapper doesn’t work for collection types

A while ago I implemented my first property wrapper in a code base I work on. I implemented an @Atomic property wrapper to make access to certain properties thread-safe by synchronizing read and write access to these properties using a dispatch queue. There are a ton of examples on the web that explain these property wrappers, how they can be used and why it's awesome. To my surprise, I found out that most, if not all of these property wrappers don't actually work for types where it matters most; collection types.

Let's look at an example that I tweeted about earlier. Given this property wrapper:

@propertyWrapper
public struct Atomic<Value> {
  private let queue = DispatchQueue(label: "com.donnywals.\(UUID().uuidString)")
  private var value: Value

  public init(wrappedValue: Value) {
    self.value = wrappedValue
  }

  public var wrappedValue: Value {
    get {
      return queue.sync { value }
    }
    set {
      queue.sync { value = newValue }
    }
  }
}

What should the output of the following code be?

class MyObject {
  @Atomic var atomicDict = [String: Int]()
}

var object = MyObject()
let g = DispatchGroup()

for index in (0..<10) {
  g.enter()
  DispatchQueue.global().async {
    object.atomicDict["item-\(index)"] = index
    g.leave()
  }
}

g.notify(queue: .main, execute: {
  print(object.atomicDict)
})

The code loops over a range ten times and inserts a new key in my @Atomic dictionary for every loop. The output I'm hoping for here is the following:

["item-0": 0, "item-1": 1, "item-2": 2 ... "item-7": 7, "item-8": 8, "item-9": 9]

Instead, here's the output of the code I showed you:

["item-3": 3]

Surely this can't be right I though when I first encountered this. So I ran the program again. Here's the output when you run the code again:

["item-6": 6]

Wait. What?

I know. It's weird. But it actually makes sense.

Because Dictionary is a value type, every time we run object.atomicDict["item-\(index)"] = index we're given a copy of the underlying dictionary because that's how the property wrapper's get works, we modify this copy and then reassign this copy as the property wrapper's wrappedValue. And because the loop runs ten times and then concurrently runs object.atomicDict["item-\(index)"] = index we first get ten copies of the empty dictionary since that's its initial state. Each copy is then modified by adding index to the dictionary for the "item-\(index)" key which leaves us with ten dictionaries, each with a single item. Next, the property wrapper's set is called for each of those ten copies. Whichever copy is scheduled to be assigned last will be the dictionaries final value.

Don't believe me? Let's modify the property wrapper a bit to help us see:

@propertyWrapper
public struct Atomic<Value> {
  private let queue = DispatchQueue(label: "com.donnywals.\(UUID().uuidString)")
  private var value: Value

  public init(wrappedValue: Value) {
    self.value = wrappedValue
  }

  public var wrappedValue: Value {
    get {
      return queue.sync {
        print("executing get and returning \(value)")
        return value
      }
    }
    set {
      queue.sync {
        print("executing set and assigning \(newValue)")
        value = newValue
      }
    }
  }
}

I've added some print statements to help us see when each get and set closure is executed, and to see what we're returning and assigning.

Here's the output of the code I showed you at the beginning with the print statements in place:

executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing get and returning [:]
executing set and assigning ["item-5": 5]
executing set and assigning ["item-7": 7]
executing set and assigning ["item-1": 1]
executing set and assigning ["item-0": 0]
executing set and assigning ["item-6": 6]
executing set and assigning ["item-3": 3]
executing set and assigning ["item-8": 8]
executing set and assigning ["item-4": 4]
executing set and assigning ["item-9": 9]
executing set and assigning ["item-2": 2]
executing get and returning ["item-2": 2]
["item-2": 2]

This output visualizes the exact process I just mentioned. Obviously, this is not what we wanted when we made the @Atomic property wrapper and applied it to the dictionary. The entire purpose of doing this is to allow multi-threaded code to safely read and write from our dictionary. The problem I've shown here applies to all collection types in Swift that are passed by value.

So how can we fix the @Atomic property wrapper? I don't know. I have tried several solutions but nothing really fits. The only solution I have seen that works is to add a special closure to your property wrapper like Vadim Bulavin shows in how post on @Atomic. While a closure like Vadim shows is effective, and makes the property wrapper play nicely with collection types it's not the kind of API I would like to have for my property wrapper. Ideally you'd be able to use the dictionary subscripts just like you normally would without thinking about it instead of using special syntax that you have to remember.

My current solution is to not use this property wrapper for collection types and instead us some kind of a wrapper type that is far more specific for your use case. Something like the following:

public class AtomicDict<Key: Hashable, Value>: CustomDebugStringConvertible {
  private var dictStorage = [Key: Value]()

  private let queue = DispatchQueue(label: "com.donnywals.\(UUID().uuidString)", qos: .utility, attributes: .concurrent,
                                    autoreleaseFrequency: .inherit, target: .global())

  public init() {}

  public subscript(key: Key) -> Value? {
    get { queue.sync { dictStorage[key] }}
    set { queue.async(flags: .barrier) { [weak self] in self?.dictStorage[key] = newValue } }
  }

  public var debugDescription: String {
    return dictStorage.debugDescription
  }
}

If we update the code from the start of this post to use AtomicDict it would look like this:

class MyObject {
  var atomicDict = AtomicDict<String, Int>()
}

var object = MyObject()
let g = DispatchGroup()

for index in (0..<10) {
  g.enter()
  DispatchQueue.global().async {
    object.atomicDict["item-\(index)"] = index
    g.leave()
  }
}

g.notify(queue: .main, execute: {
  print(object.atomicDict)
})

This code produces the following output:

["item-2": 2, "item-7": 7, "item-4": 4, "item-0": 0, "item-6": 6, "item-9": 9, "item-8": 8, "item-5": 5, "item-3": 3, "item-1": 1]

The reason this AtomicDict works is that we don't send copies of the dictionary to users of AtomicDict like we did for the property wrapper. Instead, AtomicDict is a class that users modify. The class uses a dictionary to get and set values, but this dictionary is owned and modified by one instance of AtomicDict only. This eliminates the issue we had before since we're not passing empty copies of the initial dictionary around.

In Summary

This discovery and trying to figure out why the @Atomic property wrapper doesn't work for collection types was a fun exercise in learning more about concurrency, value types and how they can produce weird but perfectly explainable results. I've not been successful in refactoring my own @Atomic property wrapper to work with all types just yet but I hope that some day I will. If you have any ideas, please do let me know and run it through the relatively simple test I presented in this post.

If you have any feedback or questions about this post, don't hesitate to reach out to me on Twitter.

Changing a publisher’s Failure type in Combine

One of Combine's somewhat painful to work with features is its error mechanism. In Combine, publishers have an Output type and a Failure type. The Output represents the values that a publisher can emit, the Failure represents the errors that a publisher can emit. This is really convenient because you know exactly what to expect from a publisher you subscribe to. But what happens when you have a slightly more complicated setup? What happens if you want to transform a publisher's output into a new publisher but the errors of the old and new publishers don't line up?

The other day I was asked a question about this. The person in question wanted to know how they could write an extension on Publisher that would transform URLRequest values into URLSession.DataTaskPublisher values so each emitted URLRequest would automatically become a network request. Here's what my initial experiment looked like (or rather, the code I would have liked to write):

extension Publisher where Output == URLRequest {
  func performRequest() -> AnyPublisher<(data: Data, response: URLResponse), Error> {
    return self
      .flatMap({ request in
        return URLSession.shared.dataTaskPublisher(for: request)
      })
      .eraseToAnyPublisher()
  }
}

Not bad, right? But this doesn't compile. The following error appears in the console:

instance method 'flatMap(maxPublishers::)' requires the types 'Self.Failure' and 'URLSession.DataTaskPublisher.Failure' (aka 'URLError') be equivalent_

In short, flatMap requires that the errors of the source publisher, and the one I'm creating are the same. That's a bit of a problem because I don't know exactly what the source publisher's error is. I also don't know how an if I can map it to URLError, or if I can map URLError to Self.Failure.

Luckily, we know that Publisher.Failure must conform to the Error protocol. This means that we can erase the error type completely, and transform it into a generic Error instead with Combine's mapError(_:) operator:

extension Publisher where Output == URLRequest {
  func performRequest() -> AnyPublisher<(data: Data, response: URLResponse), Error> {
    return self
      .mapError({ (error: Self.Failure) -> Error in
        return error
      })
      .flatMap({ request in
        return URLSession.shared.dataTaskPublisher(for: request)
          .mapError({ (error: URLError) -> Error in
            return error
          })
      })
      .eraseToAnyPublisher()
  }
}

Note that I apply mapError(_:) to self which is the source publisher and to the URLSession.DataTaskPublisher that's created in the flatMap. This way, both publishers emit a generic Error rather than their specialized error. The upside is that this code compiled. The downside is that when we subscribe to the publisher created in performRequest we'll need to figure out which error may have occurred. An alternative to erasing the error completely could be to map any errors emitted by the source publisher to a failing URLRequest:

extension Publisher where Output == URLRequest {
  func performRequest() -> AnyPublisher<(data: Data, response: URLResponse), URLError> {
    return self
      .mapError({ (error: Self.Failure) -> URLError in
        return URLError(.badURL)
      })
      .flatMap({ request in
        return URLSession.shared.dataTaskPublisher(for: request)
      })
      .eraseToAnyPublisher()
  }
}

I like this solution a little bit better because we don't lose all error information. The downside here is that we don't know which error may have occurred upstream. Neither solution is ideal but the point here is not for me to tell you which of these solutions is best for your app. The point is that you can see how to transform a publisher's value using mapError(_:) to make it fit your needs.

Before I wrap this Quick Tip, I want to show you an extension that you can use to transform the output of any publisher into a generic Error:

extension Publisher {
  func genericError() -> AnyPublisher<Self.Output, Error> {
    return self
      .mapError({ (error: Self.Failure) -> Error in
        return error
      }).eraseToAnyPublisher()
  }
}

You could use this extension as follows:

extension Publisher where Output == URLRequest {
  func performRequest() -> AnyPublisher<(data: Data, response: URLResponse), Error> {
    return self
      .genericError()
      .flatMap({ request in
        return URLSession.shared.dataTaskPublisher(for: request)
          .genericError()
      })
      .eraseToAnyPublisher()
  }
}

It's not much but it saves a couple of lines of code. Be careful when using this operator though. You lose all error details from upstream publishers when in favor of slightly better composability. Personally, I think your code will be more robust when you transform errors to the error that's needed downstream like I did in the second example. It makes sure that you explicitly handle any errors rather than ignoring them.

If you have any questions or feedback about this Quick Tip make sure to reach out on Twitter

An introduction to Big O in Swift

Big O notation. It's a topic that a lot of us have heard about, but most of us don't intuitively know or understand what it is. If you're reading this, you're probably a Swift developer. You might even be a pretty good developer already, or maybe you're just starting out and Big O was one of the first things you encountered while studying Swift.

Regardless of your current skill level, by the end of this post, you should be able to reason about algorithms using Big O notation. Or at least I want you to understand what Big O is, what it expresses, and how it does that.

Understanding what Big O is

Big O notation is used to describe the performance of a function or algorithm that is applied to a set of data where the size of that set might not be known. This is done through a notation that looks as follows: O(1).

In my example, I've used a performance that is arguably the best you can achieve. The performance of an algorithm that is O(1) is not tied to the size of the data set it's applied to. So it doesn't matter if you're working with a data set that has 10, 20 or 10,000 elements in it. The algorithm's performance should stay the same at all times. The following graph can be used to visualize what O(1) looks like:

A graph that shows O(1)

As you can see, the time needed to execute this algorithm is the same regardless of the size of the data set.

An example of an O(1) algorithm you might be familiar with is getting an element from an array using a subscript:

let array = [1, 2, 3]
array[0] // this is done in O(1)

This means that no matter how big your array is, reading a value at a certain position will always have the same performance implications.

Note that I'm not saying that "it's always fast" or "always performs well". An algorithm that is O(1) can be very slow or perform horribly. All O(1) says is that an algorithm's performance does not depend on the size of the data set it's applied to.

An algorithm that has O(1) as its complexity is considered constant. Its performance does not degrade as the data set it's applied to grows.

An example of a complexity that grows as a dataset grows is O(n). This notation communicates linear growth. The algorithm's execution time or performance degrades linearly with the size of the data set. The following graph demonstrates linear growth:

A graph that shows O(n)

An example of a linear growth algorithm in Swift is map. Because map has to loop over all items in your array, a map is considered an algorithm with O(n) complexity. A lot of Swift's built-in functional operators have similar performance. filter, compactMap, and even first(where:) all have O(n) complexity.

If you're familiar with first(where:) it might surprise you that it's also O(n). I just explained that O(n) means that you loop over, or visit, all items in the data set once. first(where:) doesn't (have to) do this. It can return as soon as an item is found that matches the predicate used as the argument for where:

let array = ["Hello", "world", "how", "are", "you"]
var numberOfWordsChecked = 0
let threeLetterWord = array.first(where: { word in
    numberOfWordsChecked += 1
    return word.count == 3
})

print(threeLetterWord) // how
print(numberOfWordsChecked) // 3

As this code shows, we only need to loop over the array three times to find a match. Based on the rough definition I gave you earlier, you might say that this argument clearly isn't O(n) because we didn't loop over all of the elements in the array like map did.

You're not wrong! But Big O notation does not care for your specific use case. If we'd be looking for the first occurrence of the word "Big O" in that array, the algorithm would have to loop over all elements in the array and still return nil because it couldn't find a match.

Big O notation is most commonly used to depict a "worst case" or "most common" scenario. In the case of first(where:) it makes sense to assume the worst-case scenario. first(where:) is not guaranteed to find a match, and if it does, it's equally likely that the match is at the beginning or end of the data set.

Earlier, I mentioned that reading data from an array is an O(1) operation because no matter how many items the array holds, the performance is always the same. The Swift documentation writes the following about inserting items into an array:

Complexity: Reading an element from an array is O(1). Writing is O(1) unless the array's storage is shared with another array or uses a bridged NSArray instance as its storage, in which case writing is O(n), where n is the length of the array.

This is quite interesting because arrays do something special when you insert items into them. An array will usually reserve a certain amount of memory for itself. Once the array fills up, the reserved memory might not be large enough and the array will need to reserve some more memory for itself. This resizing of memory comes with a performance hit that's not mentioned in the Swift documentation. I'm pretty sure the reason for this is that the Swift core team decided to use the most common performance for array writes here rather than the worst case. It's far more likely that your array doesn't resize when you insert a new item than that it does resize.

Before I dive deeper into how you can determine the Big O of an algorithm I want to show you two more examples. This example is quadratic performance, or O(n^2):

A Graph that shows O(n^2)

Quadratic performance is common in some simple sorting algorithms like bubble sort. A simple example of an algorithm that has quadratic performance looks like this:

let integers = (0..<5)
let squareCoords = integers.flatMap { i in 
    return integers.map { j in 
        return (i, j)
    }
}

print(squareCoords) // [(0,0), (0,1), (0,2) ... (4,2), (4,3), (4,4)]

Generating the squareCoords requires me to loop over integers using flatMap. In that flatMap, I loop over squareCoords again using a map. This means that the line return (i, j) is invoked 25 times which is equal to 5^2. Or in other words, n^2. For every element we add to the array, the time it takes to generate squareCoords grows exponentially. Creating coordinates for a 6x6 square would take 36 loops, 7x7 would take 49 loops, 8x8 takes 64 loops and so forth. I'm sure you can see why O(n^2) isn't the best performance to have.

The last common performance notation I want to show you is O(log n). As the name of this notation shows, we're dealing with a complexity that grows on a logarithmic scale. Let's look at a graph:

A Graph that shows O(log n)

An algorithm with O(log n) complexity will often perform worse than some other algorithms for a smaller data set. However, as the data set grows and n approaches an infinite number, the algorithm's performance will degrade less and less. An example of this is a binary search. Let's assume we have a sorted array and want to find an element in it. A binary search would be a fairly efficient way of doing this:

extension RandomAccessCollection where Element: Comparable, Index == Int {
    func binarySearch(for item: Element) -> Index? {
        guard self.count > 1 else {
            if let first = self.first, first == item {
                return self.startIndex
            }  else {
                return nil
            }
        }

        let middleIndex = (startIndex + endIndex) / 2
        let middleItem = self[middleIndex]

        if middleItem < item {
            return self[index(after: middleIndex)...].binarySearch(for: item)
        } else if middleItem > item {
            return self[..<middleIndex].binarySearch(for: item)
        } else {
            return middleIndex
        }
    }
}

let words = ["Hello", "world", "how", "are", "you"].sorted()
print(words.binarySearch(for: "world")) // Optional(3)

This implementation of a binary search assumes that the input is sorted in ascending order. In order to find the requested element, it finds the middle index of the data set and compares it to the requested element. If the requested element is expected to exist before the current middle element, the array is cut in half and the first half is used to perform the same task until the requested element is found. If the requested element should come after the middle element, the second half of the array is used to perform the same task.

A search algorithm is very efficient because the number of lookups grows much slower than the size of the data set. Consider the following:

For 1 item, we need at most 1 lookup
For 2 items, we need at most 2 lookups
For 10 items, we need at most 3 lookups
For 50 items, we need at most 6 lookups
For 100 items, we need at most 7 lookups
For 1000 items, we need at most 10 lookups

Notice how going from ten to fifty items makes the data set five times bigger but the lookups only double. And going from a hundred to a thousand elements grows the data set tenfold but the number of lookups only grows by three. That's not even fifty percent more lookups for ten times the items. This is a good example of how the performance degradation of an O(log n) algorithm gets less significant as the data set increases.

Let's overlay all three graphs I've shown you so far so you can compare them.

A mix of all mentioned Big O graphs

Notice how each complexity has a different curve. This makes different algorithms a good fit for different purposes. There are many more common Big O complexity notations used in programming. Take a look at this Wikipedia page to get an idea of several common complexities and to learn more about the mathematical reasoning behind Big O.

Determining the Big O notation of your code

Now that you have an idea of what Big O is, what it depicts and roughly how it's determined, I want to take a moment and help you determine the Big O complexity of code in your projects.

With enough practice, determining the Big O for an algorithm will almost become an intuition. I'm always thoroughly impressed when folks have developed this sense because I'm not even close to being able to tell Big O without carefully examining and thinking about the code at hand.

A simple way to tell the performance of code could be to look at the number of for loops in a function:

func printAll<T>(from items: [T]) {
    for item in items {
        print(item)
    }
}

This code is O(n). There's a single for loop in there and the function loops over all items from its input without ever breaking out. It's pretty clear that the performance of this function degrades linearly.

Alternatively, you could consider the following as O(1):

func printFirst<T>(_ items: [T]) {
    print(items.first)
}

There are no loops and just a single print statement. This is pretty straightforward. No matter how many items are in [T], this code will always take the same time to execute.

Here's a trickier example:

func doubleLoop<T>(over items: [T]) {
    for item in items {
        print("loop 1: \(item)")
    }

    for item in items {
        print("loop 2: \(item)")
    }
}

Ah! You might think. Two loops. So it's O(n^2) because in the example from the previous section the algorithm with two loops was O(n^2).

The difference is that the algorithm from that example had a nested loop that iterated over the same data as the outer loop. In this case, the loops are alongside each other which means that the execution time is twice the number of elements in the array. Not the number of elements in the array squared. For that reason, this example can be considered O(2n). This complexity is often shortened to O(n) because the performance degrades linearly. It doesn't matter that we loop over the data set twice.

Let's take a look at an example of a loop that's shown in Cracking the Coding Interview that had me scratching my head for a while:

func printPairs(for integers: [Int]) {
    for (idx, i) in integers.enumerated() {
        for j in integers[idx...] {
            print((i, j))
        }
    }
}

The code above contains a nested loop, so it immediately looks like O(n^2). But look closely. We don't loop over the entire data set in the nested loop. Instead, we loop over a subset of elements. As the outer loop progresses, the work done in the inner loop diminishes. If I write down the printed lines for each iteration of the outer loop it'd look a bit like this if the input is [1, 2, 3]:

(1, 1) (1, 2) (1, 3)
(2, 2) (2, 3)
(3, 3)

If we'd add one more element to the input, we'd need four more loops:

(1, 1) (1, 2) (1, 3) (1, 4)
(2, 2) (2, 3) (2, 4)
(3, 3) (3, 4)
(4, 4)

Based on this, we can say that the outer loop executes n times. It's linear to the number of items in the array. The inner loop runs roughly half of n on average for each time the outer loop runs. The first time it runs n times, then n-1, then n-2 and so forth. So one might say that the runtime for this algorithm is O(n * n / 2) which is the same as O(n^2 / 2) and similar to how we simplified O(2n) to O(n), it's normal to simplify O(n^2 / 2) to O(n^2).

The reason you can simplify O(n^2 / 2) to O(n^2) is because Big O is used to describe a curve, not the exact performance. If you'd plot graphs for both formulas, you'd find that the curves look similar. Dividing by two simply doesn’t impact the performance degradation of this algorithm in a significant way. For that reason, it's preferred to use the simpler Big O notation instead of the complex detailed one because it communicates the complexity of the algorithm much clearer.

While you may have landed on O(n^2) by seeing the two nested for loops immediately, it's important to understand the reasoning behind such a conclusion too because there’s a little bit more to it than just counting loops.

In summary

Big O is one of those things that you have to practice often to master it. I have covered a handful of common Big O notations in this week's post, and you saw how those notations can be derived from looking at code and reasoning about it. Some developers have a sense of Big O that's almost like magic, they seem to just know all of the patterns and can uncover them in seconds. Others, myself included, need to spend more time analyzing and studying to fully understand the Big O complexity of a given algorithm.

If you want to brush up on your Big O skills, I can only recommend practice. Tons and tons of practice. And while it might be a bit much to buy a whole book for a small topic, I like the way Cracking the Coding Interview covers Big O. It has been helpful for me. There was a very good talk at WWDC 2018 about algorithms by Dave Abrahams too. You might want to check that out. It's really good.

If you've got any questions or feedback about this post, don't hesitate to reach out on Twitter.

Using Closures to initialize properties in Swift

There are several ways to initialize and configure properties in Swift. In this week's Quick Tip, I would like to briefly highlight the possibility of using closures to initialize complex properties in your structs and classes. You will learn how you can use this approach of initializing properties, and when it's useful. Let's dive in with an example right away:

struct PicturesApi {
  private let dataPublisher: URLSession.DataTaskPublisher = {
    let url = URL(string: "https://mywebsite.com/pictures")!
    return URLSession.shared.dataTaskPublisher(for: url)
  }()
}

In this example, I create a URLSession.DataTaskPublisher object using a closure that is executed immediately when PicturesApi is instantiated. Even though this way of initializing a property looks very similar to a computed property, it's really more an inline function that runs once to give the property it's initial value. Note some of the key differences between this closure based style of initializing and using a computed property:

  • The closure is executed once when PicturesApi is initialized. A computed property is computed every time the property is accessed.
  • A computed property has to be var, the property in my example is let.
  • You don't put an = sign between the type of a computed property and the opening {. You do need this when initializing a property with a closure.
  • Note the () after the closing }. The () execute the closure immediately when PicturesApi is initialized. You don't use () for a computed property.

Using closures to initialize properties can be convenient for several reasons. One of those is shown in my earlier example. You cannot create an instance of URLSession.DataTaskPublisher without a URL. However, this URL is only needed by the data task publisher and nowhere else in my PicturesApi. I could define the URL as a private property on PicturesApi but that would somehow imply that the URL is relevant to PicturesApi while it's really not. It's only relevant to the data task that uses the URL. Using a closure based initialization strategy for my data task publisher allows me to put the URL close to the only point where I need it.

Tip:
Note that this approach of creating a data task is not something I would recommend for a complex or sophisticated networking layer. I wrote a post about architecting a networking layer a while ago and in general I would recommend that you follow this approach if you want to integrate a proper networking layer in your app.

Another reason to use closure based initialization could be to encapsulate bits of configuration for views. Consider the following example:

class SomeViewController: UIViewController {
  let mainStack: UIStackView = {
    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.spacing = 16
    return stackView
  }()

  let titleLabel: UILabel = {
    let label = UILabel()
    label.textColor = .red
    return label
  }()
}

In this example, the views are configured using a closure instead of configuring them all in viewDidLoad or some other place. Doing this will make the rest of your code much cleaner because the configuration for your views is close to where the view is defined rather than somewhere else in (hopefully) the same file. If you prefer to put all of your views in a custom view that's loaded in loadView instead of creating them in the view controller like I have, this approach looks equally nice in my opinion.

Closure based initializers can also be lazy so they can use other properties on the object they are defined on. Consider the following code:

class SomeViewController: UIViewController {
  let titleLabel: UILabel = {
    let label = UILabel()
    label.textColor = .red
    return label
  }()

  let subtitleLabel: UILabel = {
    let label = UILabel()
    label.textColor = .orange
    return label
  }()

  lazy var headerStack: UIStackView = {
    let stack = UIStackView(arrangedSubviews: [self.titleLabel, self.subtitleLabel])
    stack.axis = .vertical
    stack.spacing = 4
    return stack
  }()
}

By making headerStack lazy and closure based, it's possible to initialize it directly with its arranged subviews and configure it in one go. I really like this approach because it really keeps everything close together in a readable way. If you don't make headerStack lazy, the compiler will complain. You can't use properties of self before self is fully initialized. And if headerStack is not lazy, it needs to be initialized to consider self initialized. But if headerStack depends on properties of self to be initialized you run into problems. Making headerStack lazy solves these problems.

Closure based initialization is a convenient and powerful concept in Swift that I like to use a bunch in my projects. Keep in mind though, like every language features this feature can be overused. When used carefully, closures can really help clean up your code, and group logic together where possible. If you have any feedback or questions about this article, reach out on Twitter. I love to hear from you.

How to use SF Symbols in your apps

It’s been a while since Apple announced SF Symbols at WWDC 2019 and I remember how excited everybody was about them. The prospect of having an easy to integrate set of over 1,500 icons that you can display in nine weights sounds very appealing and has helped me prototype my ideas much quicker with good looking icons than ever before.

I haven’t heard or seen much content related to SF Symbols since they came out and I realized I hadn’t written about them at all so I figured that I’d give you some insight into SF Symbols and how you can integrate them in your app. By the end of this blog post you will know where to look for symbols, how you can integrate them and how you can configure them to fit your needs.

Browsing for symbols

The first step to using SF Symbols in your app is to figure out which symbols Apple provides, and which symbols you might need in your app. With over 1,500 symbols to choose from I’m pretty sure there will be one or more symbols that fit your needs.

To browse Apple’s SF Symbols catalog, you can download the official SF Symbols macOS app from Apple’s design resources. With this app you can find all of Apple’s symbols and you can easily view them in different weights, and you can see what they are called so you can use them in your app.

If you’d rather look for symbols in a web interface, you can use this website. Unfortunately, the website can’t show the actual symbols due to license restrictions. This means that you’ll have to look up the icons by name and use Apple’s SF Symbols app to see what they look like.

Once you’ve found a suitable symbol for your app, it’s time to use it. Let’s find out how exactly in the next section.

Using SF Symbols in your app

Using SF Symbols in your app is relatively straightforward with one huge caveat. SF Symbols are only available on iOS 13. This means that there is no way for you to use SF Symbols on iOS 12 and below. However, if your app supports iOS 13 and up (which in my opinion is entirely reasonable at this point) you can begin using SF Symbols immediately.

Once you’ve found a symbol you like, and know it’s name you can use it in your app as follows:

UIImage(systemName: "<SYMBOL NAME>")

Let's say you want to use a nice paintbrush symbol on a tab bar item, you could use the following code:

let paintbrushSymbol = UIImage(systemName: "paintbrush.fill")
let tabBarItem = UITabBarItem(title: "paint", 
                              image: paintbrushSymbol, 
                              selectedImage: nil)

Instances of SF Symbols are created as UIImage instances using the systemName argument instead of the named argument you might normally use. Pretty straightforward, right?

Find a symbol, copy its name and pass it to UIImage(systemName: ""). Simple and effective.

Configuring a symbol to fit your needs

SF Symbols can be configured to have different weights and scales. To apply a weight or scale, you apply a UIImage.SymbolConfiguration to the UIImage that will display your SF Symbol. For example, you change an SF Symbol's weight using the following code:

let configuration = UIImage.SymbolConfiguration(weight: .ultraLight)
let image = UIImage(systemName: "pencil", withConfiguration: configuration)

The above code creates an ultra light SF Symbol. You can use different weight settings from ultra light, all the way to black which is super bold. For a full overview of all available weights, refer to Apple's SF Symbols human interface guidelines.

In addition to changing a symbol's scale, you can also tweak its size by setting the symbol's scale. You can do this using the following code:

let configuration = UIImage.SymbolConfiguration(scale: .large)
let image = UIImage(systemName: "pencil", withConfiguration: configuration)

The code above applies a large scale to the symbol. You can choose between small, medium and large for your icon scale.

It's also possible to combine different configurations using the applying(_:) method on UIImage.SymbolConfiguration:

let lightConfiguration = UIImage.SymbolConfiguration(weight: .ultraLight)
let largeConfiguration = UIImage.SymbolConfiguration(scale: .large)

let combinedConfiguration = lightConfiguration.applying(largeConfiguration)

The above code creates a symbol configuration for an icon that is both ultra light and large.

One last thing I'd like to show you is how you can change an SF Symbol's color. If you're using a symbol in a tab bar, it will automatically be blue, or adapt to your tab bar's tint color. However, the default color for an SF Symbol is black. To use a different color, you can use the withTintColor(_:) method that's defined on UIImage to create a new image with the desired tint color. For example:

let defaultImage = UIImage(systemName: "pencil")!
let whiteImage = defaultImage.withTintColor(.white)

The above code can be used to create a white pencil icon that you can use wherever needed in your app.

In summary

In this week's post, you learned how you can find, use and configure Apple's SF Symbols in your apps. In my opinion, Apple did a great job implementing SF Symbols in a way that makes it extremely straightforward to use.

Unfortunately, this feature is iOS 13+ and the SF Symbols macOS app could be improved a little bit, but overall it's not too bad. I know that I'm using SF Symbols all the time in any experiments I do because they're available without any hassle.

If you have any questions, tips, tricks or feedback about this post don't hesitate to reach out on Twitter!

Find and copy Xcode device support files

Every once in a while I run into a situation where I update my iPhone to the latest iOS before I realize I'm still using an older version of Xcode for some projects. I usually realize this when Xcode tells me that it "Could not locate device support files". I'm sure many folks run into this problem.

Luckily, we can fix this by copying the device support files from the new Xcode over to the old Xcode, or by grabbing the device support files from an external source.

Copying device support files if you already have the latest Xcode installed

If you have the latest Xcode installed but need to use an older Xcode version to work on a specific project, you can safely copy the device support files from the new Xcode over to the old.

This can be done using the following command in your terminal:

cp -R /Applications/<new xcode>/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/<target ios> /Applications/<old xcode>/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/<target ios>

Make sure to replace <new xcode> with the path to your latest Xcode app, <old xcode> with your old Xcode app and <target ios> with the iOS version you wish to copy device support files for.

Tip:
I use xcversion to manage my Xcode installs. Read more in my post about having more than one Xcode version installed.

So for example, to copy device support files for iOS 13.4 from Xcode 11.4 to Xcode 11.3.1 you'd run the following command:

cp -R /Applications/Xcode-11.4.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.4 /Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.4

Let's look at the same command, formatted a little nicer:

cp -R \
  /Applications/Xcode-11.4.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.4 \
  /Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.4

Both of the above snippets copy iOS 13.4 support files from Xcode 11.4 to 11.3.1. After doing this, reboot Xcode (I always do just to be sure) and you should be able to run your Xcode 11.3.1 project on devices running iOS 13.4.

As an alternative to copying the files, you can also link them using the ln -s command in your terminal:

ln -s \
  /Applications/Xcode-11.4.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.4 \
  /Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.4

This command creates a symbolic link to the device support files instead of copying them. Both commands achieve the same result and are equally effective.

Obtaining device support files if you don't have the latest Xcode installed

Not everybody who needs device support files will have the latest Xcode available. If this is the case, I recommend that you take a look at this repository. It collects device support files for all iOS versions so you can clone that repository and copy, or link, device support files from that repository to the Xcode version you're using. You can use similar command to those that I showed in the previous section except you'd replace the old Xcode path with the path to the appropriate device support files in the clones repo.