Testing push notifications in the Simulator with Xcode 11.4

For years we've had to resort to using physical devices when testing push notifications. With Xcode 11.4, Apple finally gives developers the tools needed to test push notifications on the iOS Simulator. I'm going to assume you already know how to add push notifications to your app. If you've never added push notifications to an app before, I have a post that describes how to set up and test push notifications without a third-party service. That post should get you all set up to follow along with this post.

Sending a test push from the command line

Xcode comes with several command-line tools that allow you to run your tests, builds and other tasks directly from the Terminal in macOS. If you have Xcode installed, it should automatically install the command line tools on its first launch. If you've followed my guide on having multiple Xcode versions installed, make sure to select Xcode 11.4 or newer by running xcversion select 11.4 and replace 11.4 with the version of Xcode you want to use. Doing this will make sure the Xcode command line tools point to the correct toolchain.

When you have an iOS 13.4 or newer simulator running, all you need to send a push notification is an apns notification payload. The payload should look similar to the following:

{
  "Simulator Target Bundle": "com.donnywals.SilentPushDemo",
  "aps": {
    "alert": {
      "title": "Push on the simulator",
      "subtitle": "So cool...",
      "body": "This notification is going to show up in the simulator!"
    }
  }
}

The formatting of the push notification is the same as it would be when you send it from a server to a device. The only exception is the "Simulator Target Bundle" top-level key. This key should not be present in push notifications that you send from a production server. To send this push notification, you can run the following terminal command:

xcrun simctl push booted test_push.apns

This will send a test push notification to all booted simulators. The last argument passed to this command is the path to the file that contains the test push notification. In this case, it's a file called test_push.apns which exists in the directory as the one I'm running the command from.

The "Simulator Target Bundle" can be omitted from the file, but then the terminal command would be slightly different:

xcrun simctl push booted <your-bundle-identifier> test_push.apns

When you don't include your app's bundle identifier in the payload, you need to specify the bundle identifier of the receiving app in the command. Make sure you replace <your-bundle-identifier> with the receiving application's bundle identifier.

Sending a test push notification without the command line

If you're not comfortable using the command line to send a test push, you can also drag your test push notification directly to the simulator to have it delivered to that specific simulator immediately.

When doing this, make sure your file has .apns as its extension, and it must include the "Simulator Target Bundle" top-level key so the simulator knows which application should receive your test push.

In Summary

The ability to test push notifications in the simulator is a feature that iOS developers have wanted for a very long time, and I think that it's great that Apple has finally provided us with this ability. Especially because they managed to make the process so straightforward by keeping everything local to the machine you're developing on.

If you want to test with notifications that are actually generated and sent from your server, you still need to use a physical device because the simulator doesn't support true remote notifications just yet. However, I think this is a great step in the right direction and I'm sure I will use this feature regularly.

If you have any questions or feedback for me, make sure to reach out on Twitter.

Using Promises and Futures in Combine

So far in my Combine series I have mostly focussed on showing you how to use Combine using its built-in mechanisms. I've shown you how Combine's publishers and subscribers work, how you can use Combine for networking, to drive UI updates and how you can transform a Combine publisher's output. Knowing how to do all this with Combine is fantastic, but your knowledge is also still somewhat limited. For example, I haven't shown you at all how you can take an asynchronous operation in your existing code, and expose its result using Combine. Luckily, that is exactly what I'm going to cover in this (for now) final post in my Combine series.

In this post I will cover the following topics:

  • Understanding what Promises and Futures are
  • Wrapping an existing asynchronous operation in a Future

By the end of this post, you will be able to take virtually any async operation in your codebase and you will know how to expose it to combine using a Future.

Understanding what Promises and Futures are

The concept of Promises and Futures isn't unique to Combine or even iOS. The Javascript community has been working with Promises for a while now. We've had implementations of Promises in iOS for a while too. I even wrote a post about improving async code with PromiseKit in 2015. In Combine, we didn't get a Promises API that's identical to Javascript and PromiseKit's implementations. Instead, we got an API that is based on Futures, which is also a common concept in this kind of working area. The implementation of Combine's Futures and Promises is quite similar to the one that you'd find in Javascript on a surface level. A function can return an object that will eventually resolve with a value or an error. Sounds familiar?

In Combine, a Future is implemented as a Publisher that only emits a single value, or an error. If you examine Combine's documentation for Future, you'll find that Future conforms to Publisher. This means that you can map over Futures and apply other transformations just like you can with any other publisher.

So how does a Future work? And how do we use it in code? Let's look at a simple example:

func createFuture() -> Future<Int, Never> {
  return Future { promise in
    promise(.success(42))
  }
}

createFuture().sink(receiveValue: { value in
  print(value)
})

The createFuture function shown above returns a Future object. This object will either emit a single Int, or it fails with an error of Never. In other words, this Future can't fail; we know it will always produce an Int. This is the exact same principle as having a publisher in Combine with Never as its error. In the body of createFuture, an instance of a Future is created. The initializer for Future takes a closure. In this closure, we can perform asynchronous work, or in other words, the work we're wrapping in the Future. The closure passed to Future's initializer takes a single argument. This argument is a Promise. A Promise in Combine is a typealias for a closure that takes a Result as its single argument. When we're done performing our asynchronous work, we must invoke the promise with the result of the work done. In this case, the promise is immediately fulfilled by calling it with a result of .success(42), which means that the single value that's published by the Future is 42.

The result of a Future is retrieved in the exact same way that you would get values from a publisher. You can subscribe to it using sink, assign or a custom subscriber if you decided that you need one. The way a Future generates its output is quite different from other publishers that Combine offers out of the box. Typically, a publisher in Combine will not begin producing values until a subscriber is attached to it. A Future immediately begins executing as soon it's created. Try running the following code in a playground to see what I mean:

func createFuture() -> Future<Int, Never> {
  return Future { promise in
    print("Closure executed")
    promise(.success(42))
  }
}

let future = createFuture()
// prints "Closure executed"

In addition to immediately executing the closure supplied to the Future's initializer, a Future will only run this closure once. In other words, subscribing to the same Future multiple times will yield the same result every time you subscribe. It's important to understand this, especially if you come from an Rx background and you consider a Future similar to a Single. They have some similarities but their behavior is different.

The following is a list of some key rules to keep in mind when using Futures in Combine:

  • A Future will begin executing immediately when you create it.
  • A Future will only run its supplied closure once.
  • Subscribing to the same Future multiple times will yield in the same result being returned.
  • A Future in Combine serves a similar purpose as RxSwift's Single but they behave differently.

If you want your Future to act more like Rx's Single by having it defer its execution until it receives a subscriber, and having the work execute every time you subscribe you can wrap your Future in a Deferred publisher. Let's expand the previous example a bit to demonstrate this:

func createFuture() -> AnyPublisher<Int, Never> {
  return Deferred {
    Future { promise in
      print("Closure executed")
      promise(.success(42))
    }
  }.eraseToAnyPublisher()
}

let future = createFuture()  // nothing happens yet

let sub1 = future.sink(receiveValue: { value in 
  print("sub1: \(value)")
}) // the Future executes because it has a subscriber

let sub2 = future.sink(receiveValue: { value in 
  print("sub2: \(value)")
}) // the Future executes again because it received another subscriber

The Deferred publisher's initializer takes a closure. We're expected to return a publisher from this closure. In this case we return a Future. The Deferred publisher runs its closure every time it receives a subscriber. This means that a new Future is created every time we subscribe to the Deferred publisher. So the Future still runs only once and executes immediately when it's created, but we defer the creation of the Future to a later time.

Note that I erased the Deferred publisher to AnyPublisher. The only reason I did this is so I have a clean return type for createFuture.

The example I just showed you isn't particularly useful on its own but it does a decent job of explaining the basics of a Future. Let's move on to doing something a little bit more interesting, shall we? In the next section I will not use the Deferred publisher. I will leave it up to you to decide whether you want to wrap Futures in Deferred or not. In my experience, whether or not you should defer creation of your Futures is a case-by-case decision that depends on what your Future does and whether it makes sense to defer its creation in the context of your application.

Wrapping an existing asynchronous operation in a Future

Now that you understand the basics of how a Future is used, let's look at using it in a meaningful way. I already mentioned that Futures shine when you use them to wrap an asynchronous operation in order to make it a publisher. So what would that look like in practice? Well, let's look at an example!

extension UNUserNotificationCenter {
  func getNotificationSettings() -> Future<UNNotificationSettings, Never> {
    return Future { promise in
      self.getNotificationSettings { settings in
        promise(.success(settings))
      }
    }
  }
}

This code snippet defines an extension on the UNUserNotificationCenter object that is used to manage notifications on Apple platforms. The extension includes a single function that returns a Future<UNNotificationSettings, Never>. In the function body, a Future is created and returned. The interesting bit is in the closure that is passed to the Future initializer. In that closure, the regular, completion handler based version of getNotificationSettings is called on the current UNUserNotificationCenter instance. Inside of the completion handler, the promise closure is called with a successful result that includes the current notification settings. So what do we win with an extension like this where we wrap an existing async operation in a Future? Let's look at some code that doesn't use this Future based extension:

UNUserNotificationCenter.current().getNotificationSettings { settings in
  switch settings.authorizationStatus {
  case .denied:
    DispatchQueue.main.async {
      // update UI to point user to settings
    }
  case .notDetermined:
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { result, error in
      if result == true && error == nil {
        // We have notification permissions
      } else {
        DispatchQueue.main.async {
          // Something went wrong / we don't have permission.
          // update UI to point user to settings
        }
      }
    }
  default:
    // assume permissions are fine and proceed
    break
  }
}

In this basic example, we check the current notification permissions, and we update the UI based on the result. You might apply different abstractions in your code, but writing something like the above isn't entirely uncommon. And while it's not horrible, you can see the rightward drift that's occurring in the notDetermined case when we request notification permissions. Let's see how Futures can help to improve this code. First, I want to show a new extension on UNUserNotificationCenter that I'll be using in the refactored example:

extension UNUserNotificationCenter {
  func requestAuthorization(options: UNAuthorizationOptions) -> Future<Bool, Error> {
    return Future { promise in
      self.requestAuthorization(options: options) { result, error in
        if let error = error {
          promise(.failure(error))
        } else {
          promise(.success(result))
        }
      }
    }
  }
}

This second extension on UNUserNotificationCenter adds a new flavor of requestAuthorization(options:) that returns a Future that tells us whether we successfully received notification permissions from a user. It's pretty similar to the extension I showed you earlier in this section. Let's look at the refactored flow that I showed you earlier where we checked the current notification permissions, asked for notification permissions if needed and updated the UI accordingly:

UNUserNotificationCenter.current().getNotificationSettings()
  .flatMap({ settings -> AnyPublisher<Bool, Never> in
    switch settings.authorizationStatus {
    case .denied:
      return Just(false)
        .eraseToAnyPublisher()
    case .notDetermined:
      return UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge])
        .replaceError(with: false)
        .eraseToAnyPublisher()
    default:
      return Just(true)
        .eraseToAnyPublisher()
    }
  })
  .sink(receiveValue: { hasPermissions in
    if hasPermissions == false {
      DispatchQueue.main.async {
        // point user to settings
      }
    }
  })

There is a lot going on in this refactored code but it can be boiled down to three steps:

  1. Get the current notification settings
  2. Transform the result to a Bool
  3. Update the UI based on whether we have permissions

The most interesting step in this code is step two. I used a flatMap to grab the current settings and transform the result into a new publisher based on the current authorization status. If the current status is denied, we have asked for permissions before and we don't have notification permissions. This means that we should return false because we don't have permission and can't ask again. To do this I return a Just(false) publisher. Note that this publisher must be erased to AnyPublisher to hide its real type. The reason for this becomes clear when you look at the notDetermined case.

If the app hasn't asked for permissions before, the user is asked for notification permissions using the requestAuthorization(options:) that's defined in the extension I showed you earlier. Since this method returns a Future that can fail, we replace any errors with false. This might not be the best solution for every app, but it's fine for my purposes in this example. When you replace a publisher's error with replaceError(with:), the resulting publisher's Error is Never since any errors from publishers up the chain are now replaced with a default value and never end up at the subscriber. And since we end up with a Publishers.ReplaceError when doing this, we should again erase to AnyPublisher to ensure that both the notDetermined case and the denied case return the same type of publisher.

The default case should speak for itself. If permissions aren't denied and also not notDetermined, we assume that we have notification permissions so we return Just(true), again erased to AnyPublisher.

When we subscribe to the result of the flatMap, we subscribe to an AnyPublisher<Bool, Never> in this case. This means that we can now subscribe with sink and check the Bool that's passed to receiveValue to determine how and if we should update the UI.

This code is a little bit more complicated to understand at first, especially because we need to flatMap so we can ask for notification permissions if needed and because we need to erase to AnyPublisher in every case. At the same time, this code is less likely to grow much more complicated, and it won't have a lot of rightward drift. The traditional example I showed you before is typically more likely to grow more complicated and drift rightward over time.

In summary

In this week's post, you learned that Futures in Combine are really just Publishers that are guaranteed to emit a single value or an error. You saw that a Future can be returned from a function and that a Future's initializer takes a closure where all the work is performed. This closure itself receives a Future.Promise which is another closure. You must call the Promise to fulfill it and you pass it the result of the work that's done. Because this textual explanation isn't the clearest, I went on to show you a basic example of what using a Future looks like.

You also learned an extremely important detail of how Futures work. Namely that they begin executing the closure you supply immediately when the Future is created, and this work is only performed once. This means that subscribing to the same Future multiple times will yield the same result every time. This makes Futures a good fit for running one-off asynchronous operations.

I went on to demonstrate these principles in an example by wrapping some UNUserNotificationCenter functionality in Future objects which allowed us to integrate this functionality nicely with Combine, result in code that is often more modular, readable and easier to reason about. This is especially true for larger, more complicated codebases.

If you have feedback or questions about this post or any other content on my blog, don't hesitate to shoot me a tweet. I love hearing from you.

Five ways to get better at receiving feedback

When I just started my career as a developer there was a lot I didn't know yet. There also were a lot of things I didn't understand or had never done before. I have always been lucky enough to work in places where I was able to learn and grow as needed, and most importantly, I was allowed to make mistakes. One of the hardest things wasn't that I was constantly learning and figuring out. That actually was the fun part! The hard bit was often receiving feedback.

Whether it was feedback from clients, coworkers or teachers back in college. Something about getting feedback and learning about things I didn't get quite right, could have done better or differently always felt like it was some kind of an attack. You could argue that the feedback should have been delivered differently if that's the case, but the feedback usually was good and constructive. I just wasn't very good at receiving feedback.

Just like giving feedback, being able to receive it is a skill. It's something you learn to get better at over time. And in this week's quick tip I would like to leave you with a couple of lessons that I have learned over the years about receiving feedback in a productive, and positive manner.

1. Listen, don't defend

Something that I often found myself doing when I received feedback was coming up with counter-arguments or a defense against the feedback I was receiving. If somebody told me my code could have been better, I would tell them that my code was fine as it is. Or if somebody would point out that a specific animation wasn't quite right, I would immediately tell them that this was as good as it was going to get.

Not only does this make the feedback process very frustrating for both parties involved, but it also chips away from your credibility over time. When you tell somebody something can't be done, and a more senior developer helps you to get it done anyway, that doesn't reflect too good on you as the receiver of feedback.

It's much better to listen carefully to the feedback you're receiving. Or if the feedback is delivered via an email, Jira ticket, code review or any other digital medium, read it carefully. Don't think about any counter-arguments just yet. Just soak up whatever feedback you're getting, no matter how wrong you think it is at the time of receiving it. Especially if the feedback is delivered to you carefully and properly, you can rest assured that the person giving you feedback has the best intentions and wants to ship a fantastic product, just like you.

2. Let the feedback sink in

After listening to the feedback, or reading it all, it's very tempting to begin formulating a response immediately. Try not to do this. When you receive feedback that requires you to make a bunch of changes, or if you need to fix something you don't quite understand, or even think is impossible, chances are that your initial response is quite emotional. You worked really hard and now somebody is telling you that you didn't do a perfect job, of course you're not thrilled about that. That's normal.

It's important to let feedback sink in for a moment, let your initial knee-jerk reaction subside before you respond. I distinctly remember sending a few too many emails back to coworkers and even clients with very well thought out responses explaining why I did what I did, and why I didn't their feedback was justified, only to regret it hours later. I knew they were right, for some reason I just felt the need to explain myself and justify my work to the person giving me feedback. If I had waited an hour or so before responding, I probably would have seen things very differently, and my responses would have been different too.

3. Don't take it personally

This tip has been a game-changer for me. For the longest time, I took feedback personally. If somebody pointed out that an animation wasn't smooth, or that a label is misaligned it felt as if they were critiquing me. All I would hear is that I didn't do a good enough job implementing a design, or that my coding isn't good enough. Detaching yourself from your work is really hard, but I found that it makes receiving feedback a lot easier.

If feedback is given well, it never is an attack on you. It does not even critique on you as a person. It's an objective look at the work that was delivered. In the end, the purpose of giving and receiving feedback is to create a better product. Designers do their best to create a beautiful design, and as a developer, you try to do justice to this design while writing good code. The whole teams wants this project to succeed. So when somebody points out a flaw in the product, they are doing just that. They are not pointing out a flaw you made. They are looking at the product and pointing out a flaw in the product.

In a professional environment, nobody should be out to get you. Remember that.

4. Ask for more information if needed

Sometimes, feedback isn't delivered very well. Key information is missing, or you don't understand what somebody means. It's easy to bounce this kind of feedback off and ignore it. It's one less thing on your todo list. Unfortunately, this almost never works. The person providing feedback to you obviously cared enough to give this feedback to you. If it's not clear, ask for more information. Ask the person that gave you feedback to explain themselves.

This also applies in scenarios where you don't understand why somebody gave you certain feedback. If you're asked to refactor something in a code review but you don't understand why, you'll probably comply reluctantly. Instead, try asking this person for more information. Why do they want you to refactor? What do you gain from it? When you understand the why, implementing the feedback suddenly isn't so bad.

5. Ask for help

In my career, I have often ignored this advice. When receiving feedback, I would often explain why I did what I did, and why changing my work to address the feedback was either impossible or would take a tremendous amount of time. Being a junior developer with just a year or two experience, that's a bold statement to make. When you get feedback from a more senior developer, or designer asking you to improve something, chances are that they know that it can be done.

If this happens to you, don't jump into defense mode immediately. Instead, ask for help. Walk up to a coworker, your team lead or even the person giving you feedback and explain that you want to address their feedback but aren't quite sure how. You did a ton of research but couldn't figure out how to achieve the desired result. Doing this is good. There is no shame in admitting that you don't know how to do something and asking for help. It's much better to be honest when you're not sure about something and asking for help than it is to make a bold claim saying that something can't be done, only to be proven wrong later. That's not good for your self-esteem, and it also harms your credibility.

In summary

Receiving feedback can be hard. Especially if you're not very experienced yet. I hope that this post has given you some good tips to help you get better at receiving feedback. These tips are all derived from my own experiences as a developer over the past years and they really helped me grow as a developer.

If you have any questions about this post for me, or if you want to share your experiences with receiving feedback, don't hesitate to reach out on Twitter.

Using map, flatMap and compactMap in Combine

Oftentimes when you're working with Combine, you'll have publishers that produce a certain output. Sometimes this output is exactly what you need, but often the values that are output by a publisher need to be transformed or manipulated somehow before they are useful to their subscribers. The ability to do this is a huge part of what Combine is, what makes it so powerful, and Functional Reactive Programming (FRP) in general.

In this week's post, I will show you several common operators that you can use to transform the output from your publishers and make them more useful.

If you've been following along with my series on Combine, this article should be somewhat of a cherry on top of the knowledge you have already gained. You know how publishers and subscribers work, you know how to use Combine in a basic scenario and you know how to subscribe to publishers and publish your own values. Once you understand how you can use all this together with Combine's powerful operators, there's nothing stopping you from integrating Combine effectively in your apps.

Transforming a publisher's output with map

In Combine, publishers emit values over time. For example, we can create a very basic publisher that outputs integers as follows:

let intPublisher = [1, 2, 3].publisher

Or we can create a somewhat more elaborate publisher using a CurrentValueSubject:

let intSubject = CurrentValueSubject<Int, Never>(1)

Both of these examples can be subscribed to using Combine's sink method, and both will send integer values to the sink's receiveValue closure. Imagine that you want to display these integers on a label. You could write something like the following:

intSubject.sink(receiveValue: { int in
  myLabel.text = "Number \(int)"
})

There's nothing inherently wrong with the code above, but last week I showed that you can use the assign subscriber to update a UI element directly with the output of a publisher. That won't work if the publisher outputs integers and we want to assign its output to a label's text. To be able to use the intSubject as a direct driver for myLabel.text we need to transform its output so it becomes a string. We can do this using map:

intSubject
  .map { int in "Number: \(int)"}
  .assign(to: \.text, on: myLabel)

The preceding code is arguably a lot more readable. Instead of transforming the int into a string, and assigning the string to the label's text, we now have two distinct steps in our publisher chain. First, transform the value, then assign it to the label's text. Note that map in Combine is a lot like the map you may have used on Array or Set before.

Transforming values with compactMap

In addition to a simple map, you can also use compactMap to transform incoming values, but only publish them down to the subscriber if the result is not nil. Let's look at an example:

let optionalPublisher = [1, 2, nil, 3, nil, 4, nil, 5].publisher
  .compactMap { $0 }
  .sink(receiveValue: { int in
    print("Received \(int)")
  })

This code has the following output:

Received 1
Received 2
Received 3
Received 4
Received 5

Using compactMap in Combine has the same effect as it has on normal arrays. Non-nil values are kept while nil values are simply discarded.

This might lead you to wonder if combine also has a flatMap operator. It does. But it's slightly more complicated to grasp than the flatMap that you're used to.

Transforming values with flatMap

When you flatMap over an array, you take an array that contains other arrays, and you flatten it. Which means that an array that looks like this:

[[1, 2], [3, 4]]

Would be flatMapped into the following:

[1, 2, 3, 4]

Combine's map operations don't operate on arrays. They operate on publishers. This means that when you map over a publisher you transform its published values one by one. Using compactMap leads to the omission of nil from the published values. If publishers in Combine are analogous to collections when using map and compactMap, then publishers that we can flatten nested publishers with flatMap. Let's look at an example:

[1, 2, 3].publisher.flatMap({ int in
  return (0..<int).publisher
  }).sink(receiveCompletion: { _ in }, receiveValue: { value in
    print("value: \(value)")
  })

The preceding example takes a list of publishers and transforms each emitted value into another publisher. When you run this code, you'll see the following output:

value: 0
value: 0
value: 1
value: 0
value: 1
value: 2

When you use flatMap in a scenario like this, all nested publishers are squashed and converted to a single publisher that outputs the values from all nested publishers, making it look like a single publisher. The example I just showed you isn't particularly useful on its own, but we'll use flatMap some more in the next section when we manipulate how often a publisher outputs values.

It's also possible to limit the number of "active" publishers that you flatMap over. Since publishers emit values over time, they are not necessarily completed immediately like they are in the preceding code. If this is the case, you could accumulate quite some publishers over time as values keep coming in, and as they continue to be transformed into new publishers. Sometimes this is okay, other times, you only want to have a certain amount of active publishers. If this is something you need in your app, you can use flatMap(maxPublishers:) instead of the normal flatMap. Using flatMap(maxPublishers:) makes sure that you only have a fixed number of publishers active. Once one of the publishers created by flatMap completes, the source publisher is asked for the next value which will then also be mapped into a publisher. Note that flatMap does not drop earlier, active publishers. Instead, the publisher will wait for active publishers to finish before creating new ones. The following code shows an example of flatMap(maxPublishers:) in use.

aPublisherThatEmitsURLs
  .flatMap(maxPublishers: .max(1)) { url in
    return URLSession.shared.dataTaskPublisher(for: url)
  }

The preceding code shows an example where a publisher that emits URLs over time and transforms each emitted URL into a data task publisher. Because maxPublishers is set to .max(1), only one data task publisher can be active at a time. The publisher can choose whether it drops or accumulates generated URLs while flatMap isn't ready to receive them yet. A similar effect can be achieved using map and the switchToLatest operator, except this operator ditches the older publishers in favor of the latest one.

aPublisherThatEmitsURLs
  .map { url in
    return URLSession.shared.dataTaskPublisher(for: url)
  }
  .switchToLatest()

The map in the preceding code transforms URLs into data task publishers, and by applying switchToLatest to the output of this map, any subscribers will only receive values emitted by the lastest publisher that was output by map. This means if aPublisherThatEmitsURLs would emit several URLs in a row, we'd only receive the result of the last emitted URL.

In summary

In today's post, I showed you how you can apply transformations to the output of a certain publisher to make it better suited for your needs. You saw how you can transform individual values into new values, and how you can transform incoming values to a new publisher and flatten the output with flatMap or switchToLatest.

If you want to learn more about Combine, make sure to check out the category page for my Combine series. If you have any questions about Combine, or if you have feedback for me make sure to reach out to me on Twitter.

Updating UI with assign(to:on:) in Combine

So far in my series of posts about Combine, we have focussed on processing values and publishing them. In all of these posts, I used the sink method to subscribe to publishers and to handle their results. Today I would like to show you a different kind of built-in subscriber; assign(to:on:). This subscriber is perfect for subscribing to publishers and updating your UI in response to new values. In this post, I will show you how to use this subscriber, and I will show you how to avoid memory issues when using assign(to:on:).

Using assign(to:on:) in your code

If you've been following along with previous posts, you should at least have a basic understanding of publishers and how you subscribe to them. If you haven't and aren't sure how publishers and subscribers work, I would recommend reading the following posts before coming back to this one:

If you subscribe to a publisher using sink, and you want to update your UI in response to changes to a certain property you might write something like the following code:

class CarViewController: UIViewController {
  let car = Car()
  let label = UILabel()

  var cancellables = Set<AnyCancellable>()

  func subscribeToCarCharge() {
    car.$kwhInBattery.sink(receiveValue: { charge in
      self.label.text = "Car's charge is \(charge)"
    }).store(in: &cancellables)
  }

  // more VC code...
}  

This code should look familiar if you've read my post about publishing property changes in Combine. If you haven't, all you really need to know is that car.$kwhInBattery is a publisher that publishes a double value that represents a car's battery charge.

Note that we're using AnyCancellable's store(in:) method to retain the AnyCancellable that is returned by sink to avoid it from getting deallocated and tearing down the subscription as soon as as subscribeToCarCharge finishes executing.

All in all the above code should look familiar and it's quite effective. But what if I told you that there was a slightly nicer way to do this using assign(to:on:) instead of sink:

func subscribeToCarCharge() {
  car.$kwhInBattery
    .map { "Car's charge is \($0)" }
    .assign(to: \.text, on: label)
    .store(in: &cancellables)
}

The preceding code isn't much shorter, but it definitely is more declarative. It communicates that we want to map the Double that is provided by $kwhInBattery into a String, and that we want to assign that string to the text property on label. The assign(to:on:) method returns an AnyCancellable, just like sink. So we need to retain it to make sure it doesn't get deallocated.

Using assign(to:on:) becomes very interesting if you use an architecture where your model prepares data for your view in a way where no further processing is required, like MVVM:

struct CarViewModel {
  private let car = Car()

  let chargeRemainingText: AnyPublisher<String?, Never>

  init() {
    chargeRemainingText = car.$kwhInBattery.map {
      "Car's charge is \($0)"
    }.eraseToAnyPublisher()
  }
}

class CarViewController: UIViewController {
  let viewModel = CarViewModel()
  let label = UILabel()

  var cancellables = Set<AnyCancellable>()

    func subscribeToCarCharge() {
    viewModel.chargeRemainingText
        .assign(to: \.text, on: label)
        .store(in: &cancellables)
    }

  // More VC code...
}  

In the preceding example, all mapping is done by the ViewModel and the label can be subscribed to the chargeRemainingText publisher directly. Note that we need to convert chargeRemainingText to AnyPublisher because it's type would be Publishers.Map<Published<Double>.Publisher, String> if we didn't. With all of the above examples, you should now be able to begin using assign(to:on:) where it makes sense.

Avoiding retains cycles when using assign(to:on:)

While I was experimenting with assign(to:on:) I found out that there are cases where it might cause retain cycles. Here's an example where that might occur:

var subscription: AnyCancellable?

func subscribeToCarCharge() {
  subscription = viewModel.chargeRemainingText
    .assign(to: \.label.text, on: self)
}

The code above uses self as the target for the assignment, while self also holds on to the AnyCancellable that is returned by assign(to:on:). At this time there isn't much you can do other than implementing a workaround, or avoiding assignment to self. I personally hope this is a bug in Combine and that a future release will fix this leak.

In summary

Today you learned about Combine's assign(to:on:) subscriber. You saw that it's a special kind of subscriber that allows you to easily take values that are published by a publisher, and assign them to a property on one of your UI elements, or any other property on any other object for that matter.

You also saw that there are some considerations to keep in mind when using assign(to:on:), for example when you use it to assign a property on self.

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

Publishing property changes in Combine

In Combine, everything is considered a stream of values that are emitted over time. This means that sometimes a publisher can publish many values, and other times it publishes only a single value. And other times it errors and publishes no values at all. When your UI has to respond to changing data, or if you want to update your UI in response to a user's actions, you might consider the data and user input to both be streams of values. When we looked at networking in my previous post, it was possible to use a built-in publisher that is specialized for networking. In my introduction to Combine I showed you that you can subscribe to NotificationCenter notifications using another dedicated publisher.

There are no dedicated publishers for your own objects and properties. There are, however, publishers that allow you to publish objects of a certain type at will. These publishers are the PassthroughSubject and CurrentValueSubject publishers. Both of these publishers allow you to push values at will, but they have slightly different use cases.

Using a PassthroughSubject to publish values

The PassthroughSubject publisher is used to send values to subscribers when they become available. It doesn't have a sense of state. This means that there is no current value, a PassthroughSubject will pretty much take what you want to send on one end, and it comes out the other. Let's look at an example:

var stringSubject = PassthroughSubject<String, Never>()
stringSubject.sink(receiveValue: { value in
  print("Received value: \(value)")
})

stringSubject.send("Hello") // prints: Received value: Hello
stringSubject.send("World") // prints: Received value: World

A publisher like this useful to send temporal information, like events. For example, I think that it's likely that the built-in NotificationCenter publisher is implemented as PassthroughSubject. Let me show you an example:

let notificationSubject = PassthroughSubject<Notification, Never>()

let notificationName = Notification.Name("MyNotification")
let center = NotificationCenter.default
center.addObserver(forName: notificationName, object: nil, queue: nil) { notification in
  notificationSubject.send(notification)
}

notificationSubject.sink(receiveValue: { notification in
  print("received notification: \(notification)")
})

center.post(name: notificationName, object: nil, userInfo: ["Hello": "World!"])

In this code snippet, I created a PassthroughSubject that publishes Notification objects. I added an observer to the default NotificationCenter. In the closure that is executed for that observer when a notification is received, I tell my PassthroughSubject to send the received notification object to its subscribers. Because I used sink to subscribe to the PassthroughSubject that I created, I will then receive any notifications that are posted after I subscribed to the PassthroughSubject. Because a PassthroughSubject doesn't expose any state, I don't have access to old notifications. Do you see why I think it's likely that Apple used a PassthroughSubject or something similar to implement the built-in NotificationCenter publisher?

Using a CurrentValueSubject to publish values

In many cases where you have a model that's used to drive your UI, you are interested in a concept of state. The model has a current value. It might be a default value or a user-provided value, but there always is a value. Let's consider the following simple example:

class Car {
  var kwhInBattery = 50.0
  let kwhPerKilometer = 0.14

  func drive(kilometers: Double) {
    var kwhNeeded = kilometers * kwhPerKilometer

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

    kwhInBattery -= kwhNeeded
  }
}

The model in this example is a Car. It's an EV as you might notice by the kwhInBattery property. The battery charge is something we might want to visualize in an app. This property has state, or a current value, and this state changes over time. The battery level drains as we drive and (even though it's not implemented in this example) the battery level goes up as it charges. When you visualize this in a UI, you will want to get whatever the current value is, and then be notified of any subsequent changes. In the previous section, I showed you that a PassthroughSubject can only do the latter; it sends values to its subscribers on-demand without any sense of state or history. A CurrentValueSubject does have this sense of state. So when you subscribe to a CurrentValueSubject, you immediately get the current value of that subject, and you are notified when subsequent changes happen. Let's see this in action:

class Car {
  var kwhInBattery = CurrentValueSubject<Double, Never>(50.0)
  let kwhPerKilometer = 0.14

  func drive(kilometers: Double) {
    var kwhNeeded = kilometers * kwhPerKilometer

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

    kwhInBattery.value -= kwhNeeded
  }
}

let car = Car()

car.kwhInBattery.sink(receiveValue: { currentKwh in
  print("battery has \(currentKwh) remaining")
})

car.drive(kilometers: 200)

First, notice that we create the CurrentValueSubject with an initial value of 50.0. This will be the default value that is sent to any new subscribers before the CurrentValueSubject is mutated. Also notice that inside of the drive(kilometers:) method, we can get the current value of kwhInBattery by accessing its value property. This is something we cannot do with a PassthroughSubject. When mutating the CurrentValueSubject, we can change its value and this will automatically cause the publisher to send a new value.

The example above subscribes to the CurrentValueSubject before we call car.drive(kilometers: 200). This means that we will receive a value of 50.0 immediately after subscribing because that is the current value, and we receive a value of 21.999 after driving because the kwhInBattery value has changed. The CurrentValueSubject publisher is very useful for stateful, mutable properties like this because we can be sure that we can always get a current value to show in our UI.

Using @Published to publish values

If you've dabbled with SwiftUI a little bit, there's a good chance you've come across the @Published property wrapper. This property wrapper is a convenient way to create a publisher that behaves a lot like a CurrentValueSubject with one restriction. You can only mark properties of classes as @Published. The reason for this is that the @Published property wrapper needs to create a proxy between the value you're mutating, the object that holds the property and the publisher that is created inside of the property wrapper. This can only work well with reference types because if you'd try this with a struct you would just end up with a bunch of copies that exist on their own rather than pointing to the same object like a reference type does. So, if we have a class, we can often replace CurrentValueSubject instances with @Published properties. Let's see this in action on the Car model I created in the previous section:

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

  func drive(kilometers: Double) {
    var kwhNeeded = kilometers * kwhPerKilometer

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

    kwhInBattery -= kwhNeeded
  }
}

let car = Car()

car.$kwhInBattery.sink(receiveValue: { currentKwh in
  print("battery has \(currentKwh) remaining")
})

The @Published property wrapper allows us to access the kwhInBattery property directly like we normally would. To subscribe to the publisher that is created by the @Published property wrapper, we need to prefix the wrapped property name with a $. So in this case car.$kwhInBattery. When possible, I think it looks slightly nicer to use @Published over kwhInBattery because it's easier to access the current value of the publisher.

In Summary

In today's post, I showed you how you can transform existing properties in your code into publishers using CurrentValueSubject, PassthroughSubject and even the @Published property wrapper. You learned the differences, possibilities, and limitations of each publisher. You saw that @Published is similar to CurrentValueSubject but it's limited to being used in classes. You also learned that PassthroughSubject only forwards values with keeping any kind of state while CurrentValueSubject and @Published do have a sense of state.

With this knowledge, you should be able to begin using Combine in your projects quite effectively. What's really nice about Combine is that you don't have to integrate it into your project all at once. You can ease your way into using Combine by applying it to small parts of your code first. If you have any questions about this post, or if you have feedback for me, don't hesitate to reach out on Twitter.

Debugging network traffic with Charles

When you perform a URL Request in your app, you typically configure the request in your code and when it's all set up you pass it off to a URLSession data task, and the request should succeed if everything goes as expected. When the request is misconfigured, the server will hopefully return a useful error and you can fix your code accordingly. There are times, however, where the server does not give the information you need. Or your requests succeed but your results are not quite what you expect. It's times like these when I really wish that there was an easy way to see exactly what data my app is sending to the server and, equally important, what the server is sending to my app.

Unfortunately, there is no tool built-in on iOS that helps developers to inspect the details of their network calls. This does not mean that all hope is lost. There are third-party tools available that can proxy network traffic, allowing you to inspect exactly what data is transmitted over the proverbial wire. Today, I would like to quickly show you how you can use a tool called Charles proxy to inspect network traffic from both the simulator and your device.

Installing and configuring Charles

The first step to using Charles is, of course, installing it. You can download Charles right here on its product page. Charles is a paid tool, it costs $50 for a single license and (unfortunately) I don't have any promo codes for you, nor am I affiliated to Charles in any way. So this is in no way a promotional post where I try to get you to buy Charles for my own gains. This really is a tool that I use almost every day, and it has helped me inspect network traffic for my apps for several years now. If this tool can save you two hours of debugging over its lifetime, it's absolutely worth the $50 in my opinion. But that's just my opinion.

If you started your download at the start of the previous paragraph and read the whole bit after the download link, the download is hopefully completed by now. Follow the installation instructions (open the dmg and drag the app to your Applications folder), and launch the app. Upon its first launch, Charles will ask whether it configure your networking settings automatically. I'm not entirely sure what Charles does when you click "Grant privileges" but I personally have this setting enabled after consulting with some people I trust. This setting is not required to use Charles so you can safely click "Not Yet".

When Charles launches, you should immediately see network traffic appear as shown in the screenshot below.

Initial network traffic in Charles

A lot of the traffic that's shown will have locks displayed in front of it because it's encrypted using SSL. The origin of this traffic is your Mac. You can turn off Charles' macOS proxy in the Proxy menu, or by pressing cmd+shift+P. There is no further setup needed at this time, let's see how you can use Charles with the iOS simulator.

Using Charles with the simulator

To use Charles with the iOS simulator, you need to install Charles' certificates in the simulator. You need these certificates in order for Charles to be able to proxy the simulator's networking traffic through its interface. You can install the simulator certificates through Charles' help menu as shown in the following screenshot:

Installing root certificates for the simulator from the help menu

After installing the Charles root certificate in the iOS simulator, you can open Xcode and run your app in the simulator. Note that you must have the macOS proxy active for Charles to route the simulator's network traffic through its proxy. When you run your app, you should see your app's network traffic appear in Charles' structure list. Note that any https resource will be obfuscated as indicated by the lock icon before the hostname. This isn't great if we want to debug an endpoint that uses SSL so let's see how we can fix that next.

Help! My simulator traffic is not showing up in Charles after installing the certificates
Charles can be a little finicky when you initially set it up. When I encounter this problem on a new simulator or a fresh install of Charles I usually close Charles after installing the certificates while the simulator is open, then once the certificates are installed, I also close the simulator. When both are closes I relaunch Charles, and when Charles is launch, I relaunch the simulator. This usually fixes the problem. If it doesn't a full reboot of your machine and retrying might help too.

Enable SSL proxying in Charles

To enable SSL proxying in Charles, we need to configure Charles so that it attempts to proxy specific hosts (wildcards are allowed). This is great for debugging but if you try to proxy hosts you don't own for apps you don't own, those apps might function in ways you didn't expect. This is especially true if the apps that you're proxying traffic for use SSL pinning which is a technique to enforce the integrity of an SSL certificate at the application level. To enable SSL proxying in Charles, go to the Proxy menu and select SSL Proxying Settings or press cmd+shift+L. If you want to proxy all traffic, specify * as the hostname and leave the port field empty.

Wildcard SSL proxy settings

If you run your app again, you should see multiple hosts with a globe or other non-lock icon in front of them. It's likely that you're not interested in most of this traffic. You can use the filter option at the bottom of the sidebar to filter hosts you're interested in, or you can right-click on a host and select Focus. Selecting this will split your Structure view in the hosts you want to focus on and "Other hosts".

Inspecting the contents of a request and response

When you drill down into a hostname and select one of the files that were downloaded, Charles' detail view will show you details about a request. If you select the Contents tab, you can inspect the request's headers, HTTP body, and the server's response.

Example of network call details

The top section of the detail view contains information about your request. In the screenshot above I made the request headers visible, the Raw option in the middle of the screen would display a raw version of the request. The response information is displayed at the bottom. Because this request loaded a JSON file we get several options to view the JSON file structure and more. We can also inspect response headers and the raw request body.

All of this information can be extremely valuable if you want to verify whether you are sending the correct headers or parameters to a server, or if you want to verify that the response you're receiving from a server is the response you were expecting.

Using Charles with a real device

Running Charles on a simulator is nice, but sometimes you might want to run your device's network traffic through Charles to debug your app. You can do this by proxying your device through your machine and then installing the Charles root certificate on your device. To do this, go to the Help menu and select the appropriate option from the SSL Proxying menu as shown in the following screenshot:

Help menu with install root certificate in remote browser or device selected

Clicking this option will show you the IP address to proxy your devices through, and instructions to install the Charles root certificate on your device.

Charles proxy instructions for device

To proxy your device's traffic through your mac, make sure both devices are on the same network and tap the little i button next to the WiFi network you're connected to on your device. In the menu that appears, scroll down and select the proxy menu item. Configure the proxy manually and use the settings that are shown in the dialog that Charles presented in the previous step. Your settings should look a bit like those in the following screenshot.

Proxy settings

After setting up your device proxy, follow the instructions provided by Charles in the dialog that appeared earlier. Once completed, Charles should ask for permission to route traffic from your device through its proxy. Accept this, and traffic should immediately start to appear in Charles. When you're done debugging, don't forget to disable the proxy on your device or it won't be able to access the internet if Charles isn't running.

In summary

In today's tip, you learned how you can use Charles proxy to inspect and debug network traffic that goes on in your app. Being able to debug network calls is an essential skill in the toolbox of any iOS developer and I'm planning to write more advanced posts about Charles' capabilities in the future. Because it truly is a powerhouse.

For now, I wish you happy debugging sessions and don't hesitate to shoot me a message on Twitter if you have any questions about debugging your network calls.

How to sort an Array based on a property of an element in Swift?

This post is a short demo of using Swift's sort() and sort(by:) methods. For a more comprehensive overview of sorting, and some background information on how Swift's sorting works I recommend you take a look at this updated post

The easiest way to sort an Array in Swift is to use the sort method. This method is available for all Arrays that have Comparable elements and it sorts your array in place:

var words = ["hello", "world", "this", "is", "a", "list", "of", "strings"]
words.sort() // words is now ["a", "hello", "is", "list", "of", "strings", "this", "world"]

This modifies the input Array and sorts its elements using String conformance to Equatable.

But what if you want to sort the Array based on the length of each string? Or what if you want to sort an Array whose elements aren't Equatable? Or maybe you have a list of objects that have an id property and you want to sort by id, or maybe you want to sort blog posts by date?

You can achieve this using Array's sort(by:):

var words = ["hello", "world", "this", "is", "a", "list", "of", "strings"]
words.sort(by: { lhs, rhs in
  return lhs.count < rhs.count
}) // words is ["a", "is", "of", "this", "list", "hello", "world", "strings"]

You can use any kind of comparison logic in the closure that you pass to sort(by:). In this example, I used count which is a property of String to sort my Array by string length.

Note that sort() and sort(by:) both modify the source Array in-place. If you don't want to modify the source and create a new Array instead, you can use sorted() or sorted(by:).

let words = ["hello", "world", "this", "is", "a", "list", "of", "strings"]
let sorted = words.sorted(by: { lhs, rhs in
  return lhs.count < rhs.count
}) // sorted is ["a", "is", "of", "this", "list", "hello", "world", "strings"]

If you have any questions about this tip, or if you have feedback for me, don't hesitate to send me a Tweet!

Refactoring a networking layer to use Combine

In the past two weeks I have introduced you to Combine and I've shown you in detail how Publishers and Subscribers work in Combine. This week I want to take a more practical route and explore Combine in a real-world setting. A while ago, I published a post that explained how you can architect and build a networking layer in Swift without any third-party dependencies. If you haven't seen that post before, and want to be able to properly follow along with this post, I recommend that you skim over it and examine the end result of that post.

This week, I will show you how to refactor a networking layer that uses closures to a networking layer that's built with Combine. By the end of this post you will know and understand the following topics:

  • Converting an API that uses callbacks to one that uses Combine publishers.
  • Using Combine's built-in features to fetch data and decode it.
  • Implementing error handling in Combine.

Once you understand how you can refactor a networking client to use Combine, you should have a very rough idea of how to convert any callback-based API to use Combine.

Converting an API that uses callbacks to one that uses Combine publishers

In the networking API that we're refactoring, the full API is defined using a couple of protocols. When you're embarking on a refactoring adventure like this, it's a good idea to start by changing the protocols first and update the implementations later. The protocols that we're refactoring look as follows:

protocol RequestProviding {
  var urlRequest: URLRequest { get }
}

protocol APISessionProviding {
  func execute<T: Decodable>(_ requestProvider: RequestProviding, completion: @escaping (Result<T, Error>) -> Void)
}

protocol PhotoFeedProviding {
  var apiSession: APISessionProviding { get }

  func getPhotoFeed(_ completion: @escaping (Result<PhotoFeed, Error>) -> Void)
}

The protocols above define a very simple and basic networking layer but it's just complex enough for us to refactor. The RequestProviding protocol can remain as-is. It doesn't use callbacks so we don't have to convert it to use Combine. We can, however, refactor the ApiSessionProviding and the PhotoFeedProviding protocols. Let's start with the ApiSessionProviding protocol:

protocol APISessionProviding {
  func execute<T: Decodable>(_ requestProvider: RequestProviding) -> AnyPublisher<T, Error>
}

The execute(_:) function no longer accepts a completion parameter, and it returns AnyPublisher<T, Error> instead. Note that we're not using URLSession.DataTaskPublisher<T, Error> or a different specific Publisher object. Instead, we use a very generic publisher that's prefixed with Any. Using AnyPublisher is quite common in Combine for reasons I will explain in the next section. For now, it's important to understand that AnyPublisher is a generic, type erased, version of a Publisher that behaves just like a regular Publisher while hiding what kind of publisher it is exactly.

For the PhotoFeedProviding protocol we're going to apply a similar refactor:

protocol PhotoFeedProviding {
  var apiSession: APISessionProviding { get }

  func getPhotoFeed() -> AnyPublisher<PhotoFeed, Never>
}

The refactor for the getPhotoFeed() function is pretty much the same as the one we did earlier. We removed the completion closure and the function now returns an AnyPublisher. This time it's an AnyPublisher<PhotoFeed, Never> because we don't need getPhotoFeed() to be generic. We also don't want our publisher to publish any errors to the caller of getPhotoFeed(). This means that we'll need to handle any errors that might occur in the APISessionProviding object, and we need to do something to make sure we always end up with a valid PhotoFeed instance.

By redefining the protocols, we have refactored the API because the protocols define what the API looks like. The next step is to refactor the objects that implement these protocols.

Using Combine's built-in features to fetch data and decode it

In an earlier post, I have already shown you how you can make a network call with Combine. But I didn't quite explain what that code does, or how it works exactly. In this week's post, I will go over networking in Combine step by step so you know exactly what the code does, and how it works. Let's get started with the first step; making a network call:

struct ApiSession: APISessionProviding {
  func execute<T>(_ requestProvider: RequestProviding) -> AnyPublisher<T, Error> where T : Decodable {
    return URLSession.shared.dataTaskPublisher(with: requestProvider.urlRequest)
  }
}

The code above does not compile yet and that's okay. It takes a couple more steps to get the code to a place where everything works. The execute method returns a publisher so that other objects can subscribe to this publisher, and can handle the result of the network call. The very first step in doing this is to create a publisher that can actually make the network call. Since dataTaskPublisher(with:) returns an instance of URLSession.DataTaskPublisher, we need to somehow convert that publisher into another publisher so we can eventually return AnyPublisher<T, Error>. Keep in mind that T here is a Decodable object and our goal is to take the data that's returned by a network call and to decode this data into a model of type T.

To do this, we can use the decode(type:decoder:) operator that Combine provides. We can't use this operator directly because decode(type:decoder:) can only be used on publishers that have an Output of Data. To do this, we can map the output of the data task publisher and feed the result of this map operation to the decode operation:

struct ApiSession: APISessionProviding {
  func execute<T>(_ requestProvider: RequestProviding) -> AnyPublisher<T, Error> where T : Decodable {
    return URLSession.shared.dataTaskPublisher(for: requestProvider.urlRequest)
      .map { $0.data }
      .decode(type: T.self, decoder: JSONDecoder())
  }
}

The code above takes the Output of the URLSession.DataTaskPublisher which is (data: Data, response: URLResponse) and transforms that into a publisher whose Output is Data using the map operator. The resulting publisher is then transformed again using the decode(type:decoder:) operator so we end up with a publisher who's output is equal to T. Great, right? Not quite. We get the following compiler error at this point:

Cannot convert return expression of type 'Publishers.Decode<Publishers.Map<URLSession.DataTaskPublisher, JSONDecoder.Input>, T, JSONDecoder>' (aka 'Publishers.Decode<Publishers.Map<URLSession.DataTaskPublisher, Data>, T, JSONDecoder>') to return type 'AnyPublisher<T, Error>'

Examine the error closely, in particular, focus on the type that we're trying to return from the execute(_:) method:

Publishers.Decode<Publishers.Map<URLSession.DataTaskPublisher, JSONDecoder.Input>, T, JSONDecoder>

This type is a publisher, wrapped in another publisher, wrapped in another publisher! Whenever we apply a transformation in Combine, the transformed publisher is wrapped in another publisher that takes the upstream publisher as its input. While the chain of operations above does exactly what we want, we don't want to expose this complex chain of publishers to callers of execute(_:) through its return type. In order to hide the details of our publisher chain, and to make the return type more readable we must convert our publisher chain to a publisher of type AnyPublisher. We can do this by using the eraseToAnyPublisher after the decode(type:decoder:) operator. The final implementation of execute(_:) should look as follows:

struct ApiSession: APISessionProviding {
  func execute<T>(_ requestProvider: RequestProviding) -> AnyPublisher<T, Error> where T : Decodable {
    return URLSession.shared.dataTaskPublisher(for: requestProvider.urlRequest)
      .map { $0.data }
      .decode(type: T.self, decoder: JSONDecoder())
      .eraseToAnyPublisher()
  }
}

The code above does not handle any errors. If an error occurs at any point in this chain of publishers, the error is immediately forwarded to the object that subscribes to the publisher that's returned by execute(_:). Alternatively, we can implement some error handling using more operators to make sure that our subscribers never receive any errors. We'll implement this in the next section by adding more operators in the getPhotoFeed() method.

Implementing error handling in Combine

There are several ways to handle and emit errors in Combine. For example, if you want to use a map operator, and the mapping operation can throw an error, you can use tryMap instead. This allows you to easily send any errors that are thrown down the publisher chain. Most, if not all, of Combine's operators, have a try prefixed version that allows you to throw errors instead of handling them. In this section, we are not interested in throwing errors, but we want to catch them and transform them into something useful. Let's look at the implementation of the FeedProvider that implements the getPhotoFeed() method:

struct PhotoFeedProvider: PhotoFeedProviding {
  let apiSession: APISessionProviding

  func getPhotoFeed() -> AnyPublisher<PhotoFeed, Never> {
    return apiSession.execute(FeedService.photoFeed)
      .catch { error in
        return Just(PhotoFeed())
      }.eraseToAnyPublisher()
  }
}

Just like before, we want to return an AnyPublisher. The difference here is that we're returning a publisher that can't produce an error. To turn a publisher that might produce an error (AnyPublisher<PhotoFeed, Error> in this case) into a publisher that doesn't produce an error, we need to implement error handling. The easiest way to implement this is with the catch operator. This operator is only used if the publisher it's applied to produces an error, and you must return a new publisher that has Never as its error type from this operator. In this case, we use Combine's Just publisher to create a publisher that immediately completes with the value that's supplied to its initializer. In this case, that's an empty instance of PhotoFeed. Because the Just publisher completes immediately with the supplied value, it can never produce an error, and it's a valid publisher to use in the catch operator.

If we would want to make an attempt to handle the error but anticipate that we might fail to do so adequately, the tryCatch operator can be used. Similar to tryMap, this is an operator that follows the same rules as catch, except it can throw an error. The tryCatch operator is especially useful if you have fallback logic that would load data from a cache if possible, and throw an error if loading data from cache fails too.

Just like before, we need to use eraseToAnyPublisher() to transform the result of the catch operator to AnyPublisher to avoid having to write Publishers.Catch<AnyPublisher<PhotoFeed, Error>, Publisher> as our return type.

This wraps up all the work we need to do to implement the networking layer from my earlier post to one that uses Combine. We can use this networking layer as follows:

let session = ApiSession()
let feedProvider = PhotoFeedProvider(apiSession: session)
let cancellable = feedProvider.getPhotoFeed().sink(receiveValue: { photoFeed in
  print(photoFeed)
})

Nice and compact! And notice that we don't have to handle any completion or error events in the sink because getPhotoFeed() is guaranteed to never produce an error. Of course, never returning an error to the caller of your networking layer might not be feasible in your app. In this example, I wanted to show you how you can take a publisher that might produce an error and convert it to a publisher that is guaranteed to never produce errors.

In summary

Even though you haven't written a ton of code in this week's post, you have learned a lot. You learned how you can take a callback-based API and convert it to use Combine. We started by converting some existing protocols that describe a networking layer and from there we worked our way to refactoring some code and we even looked at error handling in Combine. The code itself is of course tailored for the networking layer we wanted to refactor, but the principles that I've demonstrated can be applied to any callback-based API.

The networking layer that we refactored is in many ways more concise, expressive and simple than the code we had before. However, stacking many transformations in Combine can lead to code that is hard to maintain and reason about. Because we can do a lot with very little code, it can be tempting to write functions that do much more that you would like in an application that does not use FRP at all. All I can say about this is that you should use your best judgment and apply new abstractions or intermediate functions, or even custom Combine transformations, where needed.

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

How to add an element to the start of an Array in Swift?

You can use Array's insert(_:at:) method to insert a new element at the start, or any other arbitrary position of an Array:

var array = ["world"]
array.insert("hello", at: 0) // array is now ["hello", "world"]

Make sure that the position that you pass to the at: argument isn't larger than the array's current last index plus one. For example:

// this is fine
var array = ["hello"]
array.insert("world", at: 1)

// this will crash
var array = ["hello"]
array.insert("world", at: 2)

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