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!

How to filter an Array in Swift?

When you have an Array of elements, and you want to drop all elements that don't match specific criteria from the Array, you're looking for Array's filter(isIncluded:) method. Let's say you have an array of words and you only want to keep the words that are longer than three characters:

let words = ["hello", "world", "this", "is", "a", "list", "of", "strings"]
let filtered = words.filter { word in
  return word.count >= 3
} // filtered is ["hello", "world", "this", "list", "strings"]

The filter(isIncluded:) method takes a closure that is executed for every element in the source Array. If you return true from this closure, the element will be included in a new, filtered Array. If you return false, the element is ignored and won't be included in the new Array. When you apply a filter on an Array, the original Array is not modified. Instead, filter(isIncluded:) creates a new Array with only the elements you want.

You can perform any kind of calculation, comparison or computation in the closure that you use, just keep in mind that the more work you in your closure, the slower your filter might become. Ideally, you should make sure that you do as little work as possible when filtering an Array.

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

Five tips to write better todos in Xcode

We all write the dreaded // TODO: and // FIXME: comments every once in a while. Sometimes we do it because we know our code can be better but we're not sure how, other times we don't have the time to write an optimal solution because of deadlines, and other times we just want to move on to more interesting problems to solve and we just slap a // TODO: on our code and call it a day.

In today's post, I would like to give you five tips to make sure your todos eventually get fixed and don't end up haunting your code unnoticed until the end of times.

1. Sign your todos with a date and your name

The first tip is possibly the most important in my opinion. If you leave a todo in your code, sign it with your name and with the current date. This makes it really clear to you and anybody on your team how long the todo has been there for, and how who left the todo there. The purpose is not to play the blame game or to point fingers but more to ask the original todo writer if the todo is still relevant, and whether there is a Jira ticket, Trello ticket or any other kind of ticket in whatever system you use to track work that must be done.

It's also possible to extract the todo author and date from git but this can sometimes be problematic, especially if code marked with the todo is moved around, or a typo is fixed because the todo will then carry an entire history. It's much more convenient to have the basic information about the todo item available at a glance.

2. Mark your todo with #warning

If your project has very few warnings and you tend to pay attention to the warnings that Xcode shows in your project, it's a good idea to make your todos show up as compiler warnings. You can do this by writing your todos as follows:

#warning("TODO: donnywals, 2020-01-08 - Improve this algorithm, I'm sure we can get this to O(1) somehow.")

When you write your todo as a #warning rather than a comment, Xcode will treat it as a compiler warning. This makes your todos stand out even more, and when combined with tip 1 your todos will be easily visible because they show up as warnings and you have all the context you might night right there in the warning.

3. Use SwiftLint to mark todos as warnings

An alternative to using #warning is to use SwiftLint. SwiftLint is a tool that helps you and your team write nicely formatted Swift code by analyzing your code and showing compiler warnings when you violate any of its rules. SwiftLint has a rule that flags any occurrences of a // TODO comment in your as compiler warnings.

If you don't like using #warning for your todos because you prefer comments, but also want to be warned about any todos in your code then SwiftLint is the way to go.

4. Remove todos if they don't get done for a while

To avoid everlasting todo items that never get fixed, make sure to go through and clean up your todos every once in a while. If you mark your todos with an author and a date, it's easy to find out which todos have been lingering for far too long and you can quickly reach out to your teammates to find out what the status of a todo is and whether it's still relevant.

A good approach here is to set aside an hour or so every couple of weeks to go through the project I'm working on to see if there are any todos that have been around for more than a month and where I don't know what their status is. I have noticed that when a todo has been around for more than a month or two, they have often become irrelevant, or I realize that implementing the todo hasn't been prioritized yet. This often leads to the todo getting prioritized, or removing it because the todo is no longer relevant.

5. Avoid todos altogether

This one is cheating a bit, but the best way to have better todos is just to avoid them completely. Whenever you find yourself writing a todo, ask yourself why. You'll often find that the answer is that you just want to move on to the next thing or because you're not sure how to do the thing you want to do, and for some reason, you decided that you don't want to figure it out right now. In many cases, you might just as well go ahead and do what needs to be done right away and avoid writing the todo altogether.

If you're reviewing a PR from one of your teammates and you spot a todo in their code, it's absolutely okay to ask them why the todo is there and try to convince them to take care of the todo now rather than at some time in the future. If the todo is reasonable, try to find out if the todo has been added to your ticketing system to make sure the work is prioritized eventually so the todo can be marked as completed.

In summary

I hope that with this list of ways to get better at writing todo items you can improve your codebase a little bit. Todos are inevitable in most codebases, but it's important that manage them well to avoid having lots of hidden todos in your code that nobody remembers or cares about anymore.

If you have any more tips on how you handle todos in your projects, make sure to let me know!

Getting started with Combine

The Combine framework. Silently introduced, yet hugely important for iOS. It didn't get any attention during the big Keynote at WWDC 2019, but as soon as folks were in the sessions they knew that Combine was going to be huge. It implements a Functional Reactive Programming (FRP) paradigm that's similar to that of Rx which is implemented by RxSwift, except it's made by Apple and has native support on all Apple platforms as long as they are running iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+ or tvOS 13+.

The fact that Apple created their own FRP framework is a big deal, and it gives off a signal to the developer community. SwiftUI makes heavy use of Combine and Apple has integrated Combine in several existing APIs as well. And since Combine is created and owned by Apple, it can be used without any third-party dependencies, and we can rest assured that Apple will continue to support Combine for the foreseeable future.

In today's post, I would like to help you get started with Combine and show you the basics of what it is, how it works, and what it can do. You will learn the following topics:

  • Understanding what Functional Reactive Programming is
  • Understanding publishers and subscribers
  • Transforming publishers

There is a lot to cover in this post, so make sure you're comfortable, grab yourself something to drink and put on your learning hat.

Understanding what Functional Reactive Programming is

In the world of FRP, your code is written in a way where data flows from one place to the other automatically through subscriptions. It uses the building blocks of Functional Programming like, for example, the ability to map one dataflow into another. FRP is particularly useful in applications that have data that changes over time.

For example, if you have a label that displays the value of a slider, you can use FRP to push the value of your slider through a stream, or publisher which will then send the new value of the slider to all subscribers of the stream, which could be the label that shows the slider value, or anything else.

In addition to driving UI updates, FRP is also incredibly useful in asynchronous programming. Consider a network request. When you make the request, you expect to get a result back eventually. Usually, you would pass a completion closure to your request which is then executed when the request is finished. In FRP, the method you call to make a request would return a publisher that will publish a result once the request is finished. The benefit here is that if you want to transform the result of the network request, or maybe chain it together with another request, your code will typically be easier to reason about than a heavily nested tree of completion closures.

I won't cover networking or UI updates in today's post. Instead, we'll go over some more basic examples of publishers and transforming values to prepare them for display. I will cover networking and UI updates in future posts.

Understanding publishers and subscribers

A Combine publisher is an object that sends values to its subscribers over time. Sometimes this is a single value, and other times a publisher can transmit multiple values or no values at all. While a publisher can publish a variable number of values, it can only emit a single completion or error event. Since it's common to represent the flow of a publisher as a so-called marble diagram, let's examine one now.

Example marble diagrams. Top exits normally, bottom with arrow

The image above contains two marble diagrams. Each diagram is shown as an arrow. Each arrow represents a publisher. The circles, or marbles, on each line, represent the values that the publisher emits. The top arrow has a line at the end. This line represents a completion event. After this line, the publisher will no longer publish any new values.

The bottom diagram ends with a cross. The cross represents an error event. Errors end the stream of values, similar to a completion event. In other words, something went wrong and the publisher will now no longer publish any new events. Every publisher in the Combine framework uses these same rules, with no exceptions. Even publishers that publish only a single value must publish a completion event after publishing their single value.

Subscribing to a simple publisher

We can model the stream of values that are published by a publisher as an array. In fact, we can even use arrays to drive simple publishers. Let's create a simple publisher that publishes a list of integers:

[1, 2, 3]
  .publisher
  .sink(receiveCompletion: { completion in
    switch completion {
    case .failure(let error):
      print("Something went wrong: \(error)")
    case .finished:
      print("Received Completion")
    }
  }, receiveValue: { value in
    print("Received value \(value)")
  })

There is a lot to unpack in the snippet above. The Combine framework adds a publisher property to Array. We can use this property to turn an array of values into a publisher that will publish all values in the array to the subscribers of the publisher.

The type of the created publisher is Publishers.Sequence<[Int], Never>. Based on this type signature, we can derive that there is a Publishers object in the Combine framework. You can look up the Publishers object in the documentation and you'll find this page. We can find the following short description for Publishers there:

A namespace for types that serve as publishers.

In other words, all the built-in publishers in the Combine framework are grouped under the Publishers enum. Each of the publishers that exist in this namespace conforms to the Publisher protocol and has a specific role. You will rarely have the need to directly create instances of the publishers contained in the Publishers enum. Instead, you'll often create them by calling methods or properties on other objects. Similar to how we created a Publishers.Sequence<[Int], Never> publisher by calling publisher on our array.

The generic types in the definition of Publishers.Sequence<[Int], Never> are [Int] and Never in the preceding example. This means that the publisher will use a sequence of type [Int] to publish values and that its failure type is Never. This tells us that the Sequence publisher will always complete successfully. This means that in the example above, the .failure case in the switch will never be hit and we can always assume success in the receiveCompletion closure of a sink where the failure type is Never. In fact, there is a special version of sink available on publishers that have a failure type of Never where you only supply a receiveValue closure.

Every publisher in Combine has an Output and a Failure type. The Output is the type of value that a publisher will push to its subscribers. In the case of our Sequence, the Output will be Int. The Failure type is Never because the publisher cannot finish with an error. Publishers that can fail will often use an object that conforms to Error as their Failure type, but you're free to specify any type you want.

Tip:
If you want to learn more about generics, check out some of the posts I have written on that topic:

You can subscribe to a publisher using the sink(receiveCompletion:receiveValue:) method. This method creates a subscriber that is subscribed to the publisher that the method was called on. It's important to note that publishers only publish values when they have subscribers. Calling sink(receiveCompletion:receiveValue:) on a publisher creates a subscriber immediately and enables the publisher to begin streaming values.

For posterity, the output of the preceding code snippet is the following:

Received value 1
Received value 2
Received value 3
Received Completion

The receiveValue closure is called whenever a new value is published by the publisher. This closure receives the latest value of the publisher as its single argument. In the completion handler, we can check whether a subscription failed with an error or if it completed normally. You can use a switch statement and pattern matching to extract the error and handle it as needed. Combine has more advanced error handling mechanisms that I won't go into in today's post. For now, it's important that you understand how publishers and subscriptions work at the surface.

Let's take a closer look at the subscription object that is created when you call sink(receiveCompletion:receiveValue:).

Keeping track of subscriptions

In the previous subsection, you learned a bit about subscribing to a publisher by calling sink(receiveCompletion:receiveValue:) on the publisher itself. In the example code, we did not store the object that's returned by sink(receiveCompletion:receiveValue:), which is perfectly fine in a Playground. However, in your applications, you need to hold on to the subscriptions you create. If you don't do this, the subscription object will be discarded as soon as the scope where you create the subscription is exited. So if you were to call sink in a function, the created subscription would cease to exist at the end of the function.

If you examine the return type of sink, you will find that it's AnyCancellable. An AnyCancellable object is a type-erased wrapper around a Cancellable subscription that you can hold on to in your view controller. You can safely hold on to your AnyCancellable objects and be assured that any subscriptions are canceled when the object that's holding on to the AnyCancellable is deallocated. You only need to call cancel yourself if you explicitly want to discard a given subscription.

Note:
If you have used RxSwift in the past, you may have worked with an object called DisposeBag, and you would have added Disposable objects to the DisposeBag. Combine does not have an equivalent of DisposeBag, but it does have an equivalent of Disposable which is the Cancellable protocol.

Consider the following example:

var subscription: AnyCancellable?

func subscribe() {
  let notification = UIApplication.keyboardDidShowNotification
  let publisher = NotificationCenter.default.publisher(for: notification)
  subscription = publisher.sink(receiveCompletion: { _ in
    print("Completion")
  }, receiveValue: { notification in
    print("Received notification: \(notification)")
  })
}

subscribe()
NotificationCenter.default.post(Notification(name: UIApplication.keyboardDidShowNotification))

In the preceding code, we use the convenient publisher(for:) method that was added to NotificationCenter to subscribe to the UIApplication.keyboardDidShowNotification notification. If you place this code in a Playground, you'll find that the print statement in the receiveValue closure is executed, but the receiveCompletion is never called. The reason for this is that NotificationCenter publisher can send an infinite number of notifications to its subscribers.

If you remove the assignment of subscription by removing subscription = before publisher.sink you will find that the receiveValue closure is never called due to the subscription being discarded as soon as the subscribe() function is done executing.

In addition to automatic cancellation of subscriptions when an AnyCancellable is deallocated, you can also explicitly cancel a subscription by calling cancel() on the AnyCancellable that contains the subscription, or directly on any object that conforms to Cancellable. To try this, you can add the following two lines after the code snippet I just showed you:

subscription?.cancel()
NotificationCenter.default.post(Notification(name: UIApplication.keyboardDidShowNotification))

If you put this in a playground, you'll find that the receiveValue closure is only called once because the subscription is canceled after the first notification is posted.

If you examine the type of object that is returned by publisher(for:), you'll find that it's a NotificationCenter.Publisher. This doesn't tell us much about the type of object and error it might publish. When you call sink(receiveCompletion:receiveValue:) on the publisher, you'll notice that the receiveCompletion closure has a single argument of type Subscribers.Completion<Never> and the receiveValue has an argument of type Notification. In other words, the Output of NotificationCenter.Publisher is Notification, and its Failure is Never. You can confirm this by looking up NotificationCenter.Publisher in the documentation and examing the Output and Failure type aliases.

Tip:
Speaking of type aliases, if you want to learn more about how and when to use them, check out my five ways to improve code with type aliases.

At this point, you know that Combine revolves around publishers and subscribers. You know that publishers only publish values when they have active subscribers and that you can quickly subscribe to publishers using the sink(receiveCompletion:receiveValue:) method. You saw two publishers in this section, a Publishers.Sequence<[Int], Never> publisher and a NotificationCenter.Publisher which publishes Notification objects and has Never as its error type. You also know that publishers will publish values until they emit either an error or a completion event. While this is really cool and useful already, let's look at another important key feature of Combine; transforming publishers.

Transforming publishers

When you subscribe to a publisher, you often don't want to use the values that it emits directly. Sometimes you'll want to format the output of a publisher so you can use it to update your UI, other times you need to extract some values from, for example, a notification for the published value to be useful.

Because Combine is a Functional Reactive Programming framework, it supports some of the foundational features that you might know from functional programming. Publishers support several transforming operators, like map or flatMap. We can use these transforming operators to transform every value that is emitted by a stream, into another value. Let's look at a marble diagram that describes how map works in Combine:

Example of a "map" marble diagram

The marble diagram above describes a publisher that emits values over time. By using the map operator on the publisher, a new publisher is created. Its type is Publisher.Map<Upstream, Output>. The Upstream generic must be another publisher, and the Output generic is the output of this new publisher. So when we use the earlier example where we subscribed to the UIApplication.keyboardDidShowNotification, we can extract the keyboard height and push that to subscribers instead of the full Notification object using the following code:

let publisher = NotificationCenter.default
  .publisher(for: notification)
  .map { (notification) -> CGFloat in
    guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
      return 0.0
    }

    return endFrame.cgRectValue.height
}

When you subscribe to the created publisher object you will receive CGFloat values rather than Notification objects. The type of publisher in the above example is Publishers.Map<NotificationCenter.Publisher, CGFloat>. In other words, its a map publisher that takes NotificationCenter.Publisher as its Upstream and CGFloat as its Output.

The general pattern in Combine is that every time you apply a transformation to a publisher, you create a new publisher that wraps the original publisher and has a new output. Sounds confusing? I know. Let me show you another example. This time we'll look at the collect operator. Let's look at the marble diagram first:

Example of a "collect" marble diagram

The pictured marble diagram shows that the collect operator takes values from a publisher, collects them into an array and sends them to its subscribers when a threshold is met. Let's look at this in a code example:

[1, 2, 3]
  .publisher
  .collect(2)
  .sink(receiveValue: { value in
    print("Received value \(value)")
  })

Note that I have omitted the receiveCompletion closure in the call to sink above. This is perfectly fine if you're not interested in completion events from a publisher, or if you know that it will never emit an error, which the Publishers.Sequence doesn't. After creating the sequence publisher, .collect(2) is called on the publisher which transforms it into a Publishers.CollectByCount publisher that wraps the original sequence publisher. This publisher uses the threshold that we supply and emits values whenever the threshold is met. The above code produces the following output in a playground:

Received value [1, 2]
Received value [3]

When a publisher completes before the threshold is met, the buffer is sent to the subscriber with the items that have been collected so far. If you don't specify a threshold at all, and call collect() on a publisher, the publisher is transformed into a Result<Success, Failure>.Publisher. This publisher uses an array of a publisher's output as the Success value of Result, and the Failure is the publisher's Failure. When the upstream publisher has completed, either with success or an error, the Result<Success, Failure>.Publisher will emit a single value that contains all values that were published, or an error.

Note that the collect() method with no threshold could cause your app to use an unbounded amount of memory if a publisher emits many events before completion. You'll usually want to specify a sensible threshold for your combine operations.

At the beginning of this section, I mentioned the flatMap operator. I'm not going to show how flatMap works in today's post. The reason is simple. Using flatMap is a fairly advanced concept where you can take a publisher that publishes other publishers, and you can use flatMap to publish the values from all of these publishers on a single new publisher. I will demonstrate flatMap in next week's post where we'll convert an existing networking layer to make use of Combine.

In summary

Today's post taught you the very basics of Combine. You learned what publishers are, and the basics of how they work. You learned that publishers push values to their subscribers until they have no more values to emit and are completed, or until they emit an error. You also learned that you can subscribe to a publisher using the sink(receiveCompletion:receiveValue:) method, and that this method creates an AnyCancellable object that contains the subscription and must be retained for as long as you need it to make sure your subscription stays alive.

After learning the basics of publishers and subscribers, I showed you the basics of how you can transform publishers of one type, into publishers of a different type, much like how map works on an array. The ability to chain together publishers, and transform them using different functions is one of the most powerful features of FRP and you will find that these transformations truly are the heart of Combine once you start using it more often.

This post is part of an ongoing series I'm planning to do where I will gradually teach you more and more about Combine using practical examples and sometimes more theoretical overviews. You can find all the posts in this series right here. This post is the first post in this series so if you're reading this post early after I published it, there probably isn't much else to read yet. Make sure to subscribe to my newsletter below, and to follow me on Twitter to be notified when I publish new content.