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.

How to check if two date ranges overlap in Swift

Dates in Swift can be compared to each other. This allows you to check whether one date comes before or after another date:

if dateOne > dateTwo {
  print("dateOne comes after dateTwo")
} else if dateOne < dateTwo {
  print("dateOne comes before dateTwo")
} else if dateOne == dateTwo {
  print("dateOne is equal to dateTwo")
}

You can also use dates in Swift to create ranges. And when you have one range, you can check whether it overlaps with another date. Let's look at an example:

struct Meeting {
  let startDate: Date
  let endDate: Date
}

func doMeetingsOverlap(_ meetingOne: Meeting, _ meetingTwo: Meeting) -> Bool {
  let leftRange = meetingOne.startDate ... meetingOne.endDate
  let rightRange = meetingTwo.startDate ... meetingTwo.endDate

  return leftRange.overlaps(rightRange)
}

In this sample code, we create a closed range using an object's start and end dates. This is done for two objects that we wanted to compare, and we then use ClosedRange's overlaps(_:) method to check if one date range has overlap with the other date range. Nice and simple!

If you have any questions about this tip, or if you have feedback for me, make sure to send me a Tweet.

Tips to ask better questions

As developers, we all get stuck sometimes. When this happens we start searching for solutions on Google, or we ask questions on Stackoverflow, on the Swift forums, the iOS Developers Slack community or other places. Over the past couple of years, I have been actively trying to help people solve problems they were stuck on and while doing that, I noticed that the art of asking good questions is not an easy one. In today's Quick tip I would like to offer you five tips that I think are important to help you get better at asking questions which will ultimately help you get better answers.

1. Provide context

When you're asking somebody a question, they are often not in the same mindset as you. Whether it's a colleague working on a different feature than you are, or somebody across the world who has never heard of you, or your project, you will need to get them on your page. I have seen people as questions like "Why doesn't my notification fire?" or "How do I fix a layout bug?" and it's really hard to answer questions like these without any context. A brief introduction that explains what you're trying to achieve or what you're working on goes a long way. For example, instead of writing the following question:

Why doesn't my notification fire?

You could write the following:

I'm working on an app that implements recurring local notifications. I have asked the user for notification permissions, and I have confirmed that permissions are granted in Settings but when I try to schedule my notifications, they never seem to be delivered. Any ideas why this might happen?

The latter version of this question provides far more information than the former and anybody who wants to help you is now in the same frame of mind as you are. A little bit of context really goes a long way for anybody who's trying to help you squash that pesky bug you're stuck on.

2. Explain what you have already tried

Far too often have I seen people ask questions that were framed decently, and then discard certain ideas or solutions that are offered because they have already tried those solutions. In programming, there is often more than one way to get from A to B, so when you're stuck on a problem or asking a question, it's really important to share the things you have already tried. This saves the person who's helping you a lot of time because they don't have to type out their ideas if they already know that they won't work for you. Additionally, you might be on the right track with what you've tried, and your problem is related to one of the solutions you've tried rather than the problem you're trying to solve.

Consider a scenario where you're trying to decode a JSON response and you just can't get it to work. If you only ask the following, you're unlikely to get a good answer:

I'm fetching data from the network, and I'm trying to decode this data into a struct. I can get the data just fine but how can I decode the JSON?

After all, you've already tried to decode the response! Instead, you might ask the following:

I'm fetching data from the network, and I'm trying to decode this data into a struct. I can get the data just fine but I can't seem to decode the JSON. I've already tried to write custom coding keys but I'm still seeing decoding errors.

This question still lacks some information which I'll address in my next tip, but the important part here is that anybody who reads this question knows that the person asking this question knows about JSONDecoder and CodingKeys. In other words, they know to put on their helping-you-to-debug-your-JSON-decoding-hats rather than their teaching-you-how-to-decode-JSON-hats.

3. Share your errors

Providing context and some solutions you may have tried is great. But in many cases, anybody who wants to help you still misses a crucial piece of information; the error message. If the person helping you doesn't know what errors you encounter (if any), it's really hard to help you debug a problem. Similarly, if you don't get an error but an unexpected outcome, it's equally important to share the unexpected outcome. Consider the following question:

I'm trying to decode some JSON data into a struct but I'm seeing an error. Any ideas?

Anybody who's willing to help you will follow up with a counter-question:

What's the error?

To streamline the process of getting help, and increasing the quality of potential answers, include the error in your question straight away:

I'm trying to decode some JSON data into a struct but I'm seeing the following error:
Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
Any ideas?

The person reading this question now immediately knows that your problem is not with the JSON parsing logic. It's with the data you're trying to decode. This is extremely valuable information to have when you're helping somebody. Error messages in the Xcode console often contain crucial bits and pieces of information that point towards the root cause of an issue.

4. Show some code (in text please)

This tip does not always apply, but it often does. If you have an error, question or problem that involves code you have already written, it's very useful to share that code in your question. Keep in mind that whoever is reading your question does not want to go through your whole codebase just to figure out what you're asking. Distill your code to the relevant bits rather than sharing all of your code. Ideally, you would even reproduce your problem with as little code as possible. Sometimes distilling a problem down to the minimum amount of code needed to reveal the problem you have already helps you fix your own problem.

When you share your code, it's okay to replace certain names if you feel like they would give away what you're working on. Just make sure to pick sensible names. Names like Foo, Bar and Baz are common in sample code, but also horrible to read and they confuse the heck out of many people. Also, don't share your code as an image if possible. When you share code, the person who's reading it will often want to copy and paste your code into an editor so they can test it, modify it otherwise try it out somehow. Sharing a screenshot of your code prevents the reader of your question from doing this (essential) experimentation and makes it likely that they decide to not look into your question any further.

5. Get to the point

If you follow all the previous tips, you have a good question. But if you go overboard, you will have a lengthy, wordy question that is buried in details and burdened with background information. When you ask your question, make sure it's easy to read, and as short as possible without omitting any information. Consider the following example:

I was handed a design from our designer and it features some weird user interface components. If you look at the attached image, you'll see a navigation bar, an image, a button, and some text. When I tap the button, the text should fade out and then the next page should appear. The navigation bar should also move up. It's absolutely essential that this works smoothly. How can I handle the button tap and kick off the transition that my designer wrote?

This example might seem like an exaggeration but I have seen questions that were written like this. Instead of writing a question like the one above, something like this is far more valuable:

My designer coded an animation for me and I need to kick off this animation when a button is tapped. I have a function that's called when I tap the button, but I'm not entirely sure how to start the animation inside of this function. I have attached the animation code and my button handler.

The rewritten question is to the point and clearly communicates what you're asking from the reader with just enough detail for them to understand where you're coming from, and where you want to go.

In summary

Asking questions can be hard, especially for people whose first language isn't English. Regardless, I think that the five tips I shared in this article should be very helpful for anybody who asks questions. The more effort you put into writing a good question, the more likely you are to get a good answer, and the quicker you will get to the answer. In some cases, not all five tips can be applied to a question, but I have found that in most cases, you can adopt them in your question one way or the other.

Do you have any extra tips to ask good questions? Or do you have feedback for me? Come find me on Twitter! I love hearing from you.

Understanding Combine’s publishers and subscribers

In my previous post about Combine, I introduced you to Functional Reactive Programming (FRP) and I've shown you can subscribe to Combine publishers using the sink(receiveCompletion:receiveValue:) method. I also showed you how you can transform the output of publishers using some of its built-in functions like map and collect. This week I want to focus more on how Combine publishers and subscribers work under the hood.

By the end of today's post, you should have a clear picture of what Combine's core components are, and how they relate to each other. The topics covered in today's post are the following:

  • Learning how publishers and subscribers are tied together
  • Creating a custom subscriber
  • Writing a custom publisher

Learning how publishers and subscribers are tied together

In last week's post I explained that publishers send values to their subscribers, and that a publisher can emit one or more values, but that they only emit a single completion (or error) event. This explanation was good enough for an introduction, but the reality is a little bit more nuanced.

A publisher/subscriber relationship in Combine is solidified in a third object, the subscription. When a subscriber is created and subscribes to a publisher, the publisher will create a subscription object and it passes a reference to the subscription to the subscriber. The subscriber will then request a number of values from the subscription in order to begin receiving those values. This is done by calling request(_:) on the subscription. The number of values that a subscriber wants to receive is communicated using a Subscribers.Demand instance. A demand can be any of the following values:

  • Subscribers.Demand.none
  • Subscribers.Demand.unlimited
  • Subscribers.Demand.max(Int)

When you create a subscription by calling sink on a Publisher, the created subscriber calls request(_:) on its subscription with a value of Subscribers.Demand.unlimited because it wants to receive all values that are published, no matter how many. In most cases, this is exactly what you want, but sometimes a subscriber might want to limit the number of items it takes from a publisher. If you want to limit the number of items received from a publisher, you can call request with Subscribers.Demand.max(Int) where Int is replaced with the number of items that should be received. As new values come in, a subscriber can update the number of values it wants to receive from the publisher. Note that this number can only be incremented. This means that a subscriber that is created with a demand of .unlimited, it will always receive all values from the publisher it's subscribed to. If you call request(_:) with a demand of .max(1) and subsequently, call it with a demand of .max(10), the total number of items sent to the subscriber is 11 since demands are always additive.

When a new value is generated for a publisher, it's the subscription's job to mediate between the publisher and the subscriber and to make sure that subscribers don't receive more values than they request. Subscriptions are also responsible for retaining and releasing subscribers. The following diagram visualizes the complex relationship between subscribers, publishers, and subscriptions:

The subscriber flow

As you can see in the image above, the publisher doesn't really do an awful lot. It's mostly the subscription and the subscriber that interact with each other. In many cases, you will not need the publisher itself once a subscription is established, but this is not a given. The subscriber will ask the subscription for values, and the subscription will take care of sending these values to the subscriber. This does not mean that the subscription must do all of the work, that it must generate the values on its own, or that you can't use the publisher to generate values at all. You're free to implement your logic as you see fit as long as your implementation fits the contract that is established by the way objects relate to each other in Combine. For example, it's perfectly valid for a subscription to ask a publisher for the "next" value if that makes sense. It's equally valid for a subscription to execute a URLRequest and forward its result to a subscriber without consulting the publisher at all as long as it doesn't break the established contract.

To deepen your understanding of how publishers, subscriptions, and subscribers relate to each other I will show you how you can implement your own version of a URLSession.DataTaskPublisher. I briefly showed how you can use DataTaskPublisher in an earlier post I wrote about supporting low data mode in your app. My purpose today is not to teach you how to properly do networking in Combine. If you want to learn more about networking in Combine, you can check out this post. My purpose today is merely to show you how you can create your own publisher, subscription and subscriber triad from scratch. We'll start implementing a custom subscriber. After doing that I will show you how to implement a custom publisher and subscription.

Creating a custom subscriber

In the previous section, I explained that subscribers request (or demand) a number of values from a subscription object. You also learned that a publisher receives a subscriber, creates a subscription and ties the subscription and subscriber together. When you implement your own subscriber, part of this process happens outside of the subscriber, but there is also some work you need to do in your custom subscriber. Before we implement our own subscriber, let's look at the protocol requirements of the Subscriber protocol:

public protocol Subscriber : CustomCombineIdentifierConvertible {

  associatedtype Input
  associatedtype Failure : Error

  func receive(subscription: Subscription)
  func receive(_ input: Self.Input) -> Subscribers.Demand
  func receive(completion: Subscribers.Completion<Self.Failure>)
}

The Subscriber protocol has two associated types, Input and Failure. The concrete values for these associated types must match those of the publisher that it wants to subscribe to. If a subscriber expects Int as its Input, it can't subscribe to a publisher that has String as its Output. The same is true for the Failure associated type.

The three different flavors of receive that are required by the protocol are all used at a different time in the subscription lifecycle. The first, receive(subscription:) is called when a publisher creates and assigns a subscription object to the subscriber. At that point, the subscriber communicates its initial demand to the subscription. I will demonstrate this in a moment.

Next, receive(_:) is called every time the subscription pushes a new object to the subscriber. The subscriber returns a Subscription.Demand to the subscription to communicate the number of items it wants to receive now. As mentioned before, this demand is added to any demands that were sent to the subscription earlier. When the final value is generated, the subscription will call receive(completion:) with the result of the sequence. This method is only called once and concludes the stream of new values.

Before we implement a custom subscriber for the data task publisher that I mentioned at the end of the previous section, I want to show you a simple subscriber that takes a sequence of Int objects:

class IntSubscriber: Subscriber {
  typealias Input = Int
  typealias Failure = Never

  func receive(subscription: Subscription) {
    print("Received subscription")
    subscription.request(.max(1))
  }

  func receive(_ input: Input) -> Subscribers.Demand {
    print("Received input: \(input)")
    return .none
  }

  func receive(completion: Subscribers.Completion<Never>) {
    print("Received completion: \(completion)")
  }
}

(0...6).publisher.subscribe(IntSubscriber())

The typealias declarations in the code above specify that this subscriber takes Int as its input, and because Never is the failure type, we don't expect publishers that this subscriber subscribes to emit failures. This subscriber will only work on publishers that emit Int values, and no errors.

In the receive(subscription:) method, the subscriber receives a subscription and immediately requests a demand of .max(1). This means that this subscriber wants to receive a single value. The receive(_:) method receives a value and then returns .none. This means that we don't want to alter the demand of this publisher when we receive a new value. If we would return a demand of .max(1) here, we would increase the demand by one, and receive the next value. The last method, receive(completion:) is called when the subscriber finishes.

If you put this code in a playground, you'll find that the output is the following:

Received subscription
Received input: 0

As expected we only receive a single value here. What's interesting is that we never receive a completion event. By calling subscription.request(.max(1)), the subscriber explicitly communicates that it wants to receive a single event. No more, no less. Once the subscription has pushed that single value to the subscriber and the demand is not bumped in response to receiving that single value, the subscriber is discarded and the subscription is invalidated. If we want to receive all the values that are published in the preceding example, we need to update the receive(subscription:) method as follows:

func receive(subscription: Subscription) {
  print("Received subscription")
  subscription.request(.unlimited)
}

By requesting an unlimited number of values, all values are pushed from the subscription to the subscriber until the publisher sends a completion event. Makes sense, right? Let's look at the subscriber that's used in the next section when I show you how to build a custom version of a DataTaskPublisher:

// Skeleton for a subscriber, we'll finish it later in the article

class DecodableDataTaskSubscriber<Input: Decodable>: Subscriber {
  typealias Failure = Error

  func receive(subscription: Subscription) {
    print("Received subscription")
    subscription.request(.unlimited)
  }

  func receive(_ input: Input) -> Subscribers.Demand {
    print("Received value: \(input)")
    return .none
  }

  func receive(completion: Subscribers.Completion<Error>) {
    print("Received completion \(completion)")
  }
}

This subscriber is very similar to the IntSubscriber. The main difference is that the Input is defined as a generic type instead of a typealias. The Failure type is Error instead of Never because network requests can fail.

Note:
The subscriber in this code snippet is not yet in a properly usable state, I will show you how to finish and use this subscriber later. First, I want to show you how to create your own publisher and subscription objects.

Writing a custom publisher

Before we continue, I want to make sure you understand that writing a custom publisher is not something that you'll likely do in practice. It's also something that Apple does not recommend you do. In most cases, the default publishers and subjects that Combine exposes are more than good enough. In fact, I'm sure that the implementation I'm about to show you is not perfect, and that's okay. The purpose of the code in this section is to give you an idea of how Combine works under the hood by implementing a rudimentary publisher, not to write a clever, usable publisher that you might want to use in your projects.

Like I mentioned before, in this post I will show you a custom version of a DataTaskPublisher. This custom publisher will automatically decode Data into a Decodable model. The first step in building a custom publisher is to define the publisher itself. Before we do this, let's look at the Publisher protocol:

public protocol Publisher {

  associatedtype Output
  associatedtype Failure : Error

  func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

The most interesting part of the Publisher protocol is the receive(subscriber:) method. This method constrains the generic subscriber S by requiring that S conforms to the Subscriber protocol and it ensures that the Failure and Output for the Publisher match the Failure and Input of the Subscriber. This receive(subscriber:) method is called immediately when the subscribe(subscriber:) method is called on the publisher. In the receive(subscriber:) method you are expected to create a Subscription object and pass it to the subscriber.

The following code defines the custom data task publisher I wanted to show you:

extension URLSession {
  struct DecodedDataTaskPublisher<Output: Decodable>: Publisher {
    typealias Failure = Error

    let urlRequest: URLRequest

    func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
      let subscription = DecodedDataTaskSubscription(urlRequest: urlRequest, subscriber: subscriber)
      subscriber.receive(subscription: subscription)
    }
  }

  func decodedDataTaskPublisher<Output: Decodable>(for urlRequest: URLRequest) -> DecodedDataTaskPublisher<Output> {
    return DecodedDataTaskPublisher<Output>(urlRequest: urlRequest)
  }
}

The preceding code defines a DecodedDataTaskPublisher<Output, Decodable> struct in an extension on URLSession. It also defines a convenience method that enables users of this custom publisher to create a DecodedDataTaskPublisher<Output, Decodable> in a way that's similar to how a normal DataTaskPublisher is created. The DecodedDataTaskPublisher has a property that holds the URLRequest that should be executed. In the receive(subscriber:) method, an instance of DecodedDataTaskSubscription is created. The subscription object receives the URLRequest and the subscriber, and the subscription is passed to the subscriber's receive(subscription:) method that you saw in the previous section.

All that's left to complete our publisher, subscription and subscriber triad, is to write the subscription object. Let's look at the Subscription protocol first:

public protocol Subscription : Cancellable, CustomCombineIdentifierConvertible {
  func request(_ demand: Subscribers.Demand)
}

The Subscription protocol itself only requires a request(_:) method to be implemented. Because Subscription inherits from the Cancellable protocol, subscriptions are also required to implement a cancel() method that's used to free up any resources that the subscription holds on to, like the subscriber itself for example. The request(_:) method is used by the subscriber to communicate the number of values the subscriber wants to receive and when you write a custom subscription it's your responsibility to honor this demand correctly and to not send more values than requested by the subscriber.

Now that you know a bit about the Subscription protocol, let's look at the implementation of the DecodedDataTaskSubscription object:

extension URLSession.DecodedDataTaskPublisher {
  class DecodedDataTaskSubscription<Output: Decodable, S: Subscriber>: Subscription
    where S.Input == Output, S.Failure == Error {

    private let urlRequest: URLRequest
    private var subscriber: S?

    init(urlRequest: URLRequest, subscriber: S) {
      self.urlRequest = urlRequest
      self.subscriber = subscriber
    }

    func request(_ demand: Subscribers.Demand) {
      if demand > 0 {
        URLSession.shared.dataTask(with: urlRequest) { [weak self] data, response, error in
          defer { self?.cancel() }

          if let data = data {
            do {
              let result = try JSONDecoder().decode(Output.self, from: data)
              self?.subscriber?.receive(result)
              self?.subscriber?.receive(completion: .finished)
            } catch {
              self?.subscriber?.receive(completion: .failure(error))
            }
          } else if let error = error {
            self?.subscriber?.receive(completion: .failure(error))
          }
        }.resume()
      }
    }

    func cancel() {
      subscriber = nil
    }
  }
}

There's a lot of code in the preceding snippet. The most important part of this code is the request(_:) method. If this method is called with a demand that's larger than zero, a regular data task is created and kicked off. The completion handler for this data task uses the defer statement to call cancel on the subscription after the response from the data task is handled. We do this because once the data task is completed, the subscription will emit no further values because its job is done.

If the data task succeeded, and the callback receives data, an attempt is made to decode the data into the Output type. If this succeeds, the resulting object is passed to the subscriber so it can receive and handle the decoded response. Immediately after, we send a completion event to indicate that we finished successfully. If the decoding fails, or if we receive an error instead of data, we complete the subscription with .failure(error) so the subscriber can handle the error.

Before we attempt to use our custom subscriber, publisher and subscription together, let's try to use just the publisher and subscription first. We can do this by subscribing to the custom publisher using sink:

struct SomeModel: Decodable {}
var cancellable: AnyCancellable?

func makeTheRequest() {
  let request = URLRequest(url: URL(string: "https://www.donnywals.com")!)
  let publisher: URLSession.DecodedDataTaskPublisher<SomeModel> = URLSession.shared.decodedDataTaskPublisher(for: request)
  cancellable = publisher.sink(receiveCompletion: { completion in
    print("Received completion: \(completion)")
  }, receiveValue: { value in
    print("Received value: \(value)")
  })
}

makeTheRequest()

In the preceding example, I wrapped the code to make a request using the custom publisher in a function to simulate real-world usage. If you remove cancellable = from the code above, you will notice that the AnyCancellable returned by sink is not retained, and the completion and value closures are never called. If you run the above code as-is, the completion closure is called with a failure event. This is expected because the endpoint https://www.donnywals.com does not return a valid JSON response. While it's pretty neat that our custom publisher and subscription seem to work as intended, let's try to use it with our custom subscriber instead of sink:

struct SomeModel: Decodable {}
var cancellable: AnyCancellable?

func makeTheRequest() {
  let request = URLRequest(url: URL(string: "https://www.donnywals.com")!)
  let publisher: URLSession.DecodedDataTaskPublisher<SomeModel> = URLSession.shared.decodedDataTaskPublisher(for: request)
  let subscriber = DecodableDataTaskSubscriber<SomeModel>()
  publisher.subscribe(subscriber)
}

makeTheRequest()

If you run the code above, you'll find that only the Received subscription statement from the custom subscriber is printed. It appears that the subscriber is deallocated before it receives any events from the subscription. To confirm this, add the following code to DecodableDataTaskSubscriber:

deinit {
  Swift.print("deinit subscriber")
}

This will show you when the subscriber is deallocated, and if you run the code again you'll see that deinit subscriber is printed immediately after Received subscription. The reason for this is simple and complex at the same time.

When you call subscribe(_:) on a publisher, it creates a subscription and passes this subscription to the subscriber. It does not retain the subscription it creates, which means that the subscription will be deallocated once the publishers' subscribe(_:) method finishes unless another object retains the subscription.

When the subscription is initialized, it receives the subscriber object that it will push values to and the subscription stores this subscriber object for future reference. The subscriber itself only accesses the subscription once when its receive(subscription:) method is called. In our current implementation, it does not retain the subscription at all. And since the subscription itself is also not held on to outside of makeRequest, both the subscription and the subscriber are both deallocated by the time makeRequest exits.

Based on the description above, we need an object to hold on to our subscription to prevent it from being deallocated. Preferably, this object is Cancellable so it can be used to cancel the subscription if needed. Based on the following excerpt from Apple's documentation on Subscriber, the subscriber itself is a good candidate for retaining the subscription and canceling it later:

You connect a subscriber to a publisher by calling the publisher’s subscribe(_:) method. After making this call, the publisher invokes the subscriber’s receive(subscription:) method. This gives the subscriber a Subscription instance, which it uses to demand elements from the publisher, and to optionally cancel the subscription.

Update the DecodableDataTaskSubscriber so it's implementation looks as follows:

class DecodableDataTaskSubscriber<Input: Decodable>: Subscriber, Cancellable {
  typealias Failure = Error

  var subscription: Subscription?

  func receive(subscription: Subscription) {
    print("Received subscription")
    self.subscription = subscription
    subscription.request(.unlimited)
  }

  func receive(_ input: Input) -> Subscribers.Demand {
    print("Received value: \(input)")
    return .none
  }

  func receive(completion: Subscribers.Completion<Error>) {
    print("Received completion \(completion)")
    cancel()
  }

  func cancel() {
    subscription?.cancel()
    subscription = nil
  }
}

By conforming DecodableDataTaskSubscriber to Cancellable and holding on to the subscription it receives in receive(subscription:) we created somewhat of a retain cycle which keeps all objects alive. The subscriber holds on to its subscription and the subscription holds on to the subscriber. By canceling the subscription and setting it to nil when the subscriber's cancel method, we make sure to break the cycle when needed. Surprisingly enough, I have found that Subscribers.Sink seems to work in the same way. The major difference is that when you use sink to subscribe to a publisher, the Subscribers.Sink is immediately wrapped in an AnyCancellable instance that, when deallocated, calls cancel() on the cancellable object it wraps which immediately breaks any retain cycles that may have existed.

With the code above, you have completed our triad of a custom publisher, subscription and subscriber. That's a huge achievement! Even though you won't be building all of these components yourself in most cases, it's good to know what happens on the inside and I hope you now know a lot more about Combine and what it does behind the curtain.

In summary

Today's post is a long one, but it's also an important one. I hope that I was able to show you how publishers, subscribers, and subscriptions work in the Combine framework, and what role they fulfill. All publishers that you use in Combine are built by composing the same building blocks in one way or the other. What's important is that you understand what these building blocks are, and what role they fulfill.

To summarize, when a subscriber wants to subscribe to a publisher, the publisher creates a subscription. The subscriber is passed to this subscription. The subscription is then passed to the subscriber so the subscriber can ask for an initial number of items. The subscription will then push values to the subscriber, and the subscriber can adjust its demand every time a value is received. If the subscriber asks for a number of items that is greater than, or equal to the number of items pushed by the subscription, it will eventually receive a completion or error event. If the subscriber demands fewer values than the subscription will push, the subscriber will not receive a completion event.

Since this post is quite long and complex, I would recommend that you let this sink in for a moment. If you're confused or overwhelmed, that's okay. Come back to this post again at a later time. And most importantly, try to follow along with the code presented in this post. Put it in a Playground and experiment with it. It will eventually make sense.

if you have any questions for me, or if you have feedback, make sure to let me know on Twitter.

Removing a specific object from an Array in Swift

Arrays in Swift are powerful and they come with many built-in capabilities. One of these capabilities is the ability to remove objects. If you want to remove a single, specific object from an Array and you know its index, you can use remove(at:) to delete that object:

var array = [1, 2, 3]
array.remove(at: 0) // array is now [2, 3]

If you don't know the object's position in the Array and the Array's elements conform to Equatable, you can look up the first index of the object you're looking for with firstIndex(of:) and you can then proceed to delete the item at that position:

var array = ["hello", "world"]
if let index = array.firstIndex(of: "hello") {
  array.remove(at: index) // array is now ["world"]
}

Note that this will only remove a single object, so if you have an array with repeating values you will only remove the first match:

var array = ["hello", "world", "hello"]
if let index = array.firstIndex(of: "hello") {
  array.remove(at: index) // array is now ["world", "hello"]
}

If you want to remove all matches for an object, you can use the closure based removeAll(where:):

var array = ["hello", "world"]
array.removeAll { value in
  return value == "hello"
}
// array is now ["world"]

The closure you pass to removeAll(where:) is executed for every element in the Array. When you return true from the closure, the element is removed from the Array. When you return false, the element remains in the Array.

Performance of removing elements from an array

While using removeAll(where:) is convenient, its performance is worse than using firstIndex(of:) to find an index and then removing the element using remove(at:) since firstIndex(of:) stops iterating your array as soon as a match is found while removeAll(where:) keeps iterating until it processed all items in your array. So while removeAll(where:) is more convenient to use, firstIndex(of:) alongside remove(at:) are better for performance.

If you have any questions about this tip, or if you spot inaccuracies or have feedback for me, don't hesitate to reach out on X or Threads!