Reading and writing Property List files with Codable in Swift

You have probably seen and used a property list file at some point in your iOS journey. I know you have because every iOS app has an Info.plist file. It's possible to create and store your own .plist files to hold on to certain data, like user preferences that you don't want to store in UserDefaults for any reason at all. In this week's Quick Tip you will learn how you can read and write data from and to property list files using Swift's Codable protocol.

Defining a model that can be stored in a property list

Because Swift has special PropertyListEncoder and PropertyListDecoder objects, it's possible to define the model that you want to store in a property list using Codable:

struct APIPreferences: Codable {
  var apiKey: String
  var baseURL: String
}

This model is trivial but you can create far more complex models if you want. Any model that conforms to Codable can be used with property lists. If you haven't worked with Codable before, check out this post from my Antoine van der Lee to get yourself up to speed. His post is about JSON parsing, but everything he writes about defining models applies to property lists as well.

Loading a model from a property list

We can load plist files from the filesystem using the FileManager object. Let's dive right in with some code; this is a Quick Tip after all.

class APIPreferencesLoader {
  static private var plistURL: URL {
    let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    return documents.appendingPathComponent("api_preferences.plist")
  }

  static func load() -> APIPreferences {
    let decoder = PropertyListDecoder()

    guard let data = try? Data.init(contentsOf: plistURL),
      let preferences = try? decoder.decode(APIPreferences.self, from: data)
      else { return APIPreferences(apiKey: "", baseURL: "") }

    return preferences
  }
}

I defined a simple class here because this allows me to use the APIPreferenceLoader in a brief example at the end of this post.

The plistURL describes the location of the property list file on the file system. Since it's a file that we want to create and manage at runtime, it needs to be stored in the documents directory. We could store an initial version of the plist file in the bundle but we'd always have to copy it over to the documents directory to update it later because the bundle is read-only. You might use the following code to perform this copy step:

extension APIPreferencesLoader {
  static func copyPreferencesFromBundle() {
    if let path = Bundle.main.path(forResource: "api_preferences", ofType: "plist"),
      let data = FileManager.default.contents(atPath: path),
      FileManager.default.fileExists(atPath: plistURL.path) == false {

      FileManager.default.createFile(atPath: plistURL.path, contents: data, attributes: nil)
    }
  }
}

This code extracts the default preferences from the bundle and checks whether a stored property list exists in the documents directory. If no file exists in the documents directory, the data that was extracted from the bundled property list is copied over to the path in the documents directory so it can be modified by the application later.

The load() method from the initial code sample uses a PropertyListDecoder to decode the data that's loaded from the property list in the bundle into the APIPreferences model. If you're familiar with decoding JSON in Swift, this code should look familiar to you because it's the exact same code! Convenient, isn't it?

If we couldn't load the property list in the documents directory, or if the decoding failed, load() returns an empty object by default.

Writing a model to a property list

If you have a model that conforms to Codable as I defined in the first section of this tip, you can use a PropertyListEncoder to encode your model into data, and you can use FileManager to write that data to a plist file:

extension APIPreferencesLoader {
  static func write(preferences: APIPreferences) {
    let encoder = PropertyListEncoder()

    if let data = try? encoder.encode(preferences) {
      if FileManager.default.fileExists(atPath: plistURL.path) {
        // Update an existing plist
        try? data.write(to: plistURL)
      } else {
        // Create a new plist
        FileManager.default.createFile(atPath: plistURL.path, contents: data, attributes: nil)
      }
    }
  }
}

This code checks whether our property list file exists in the documents directory using the plistURL that I defined in an earlier code snippet. If this file exists, we can simply write the encoded model's data to that file and we have successfully updated the property list in the documents directory. If the property list wasn't found in the documents directory, a new file is created with the encoded model.

Trying out the code from this post

If you've been following along, you can try the property list reading and writing quite easily with SwiftUI:

struct ContentView: View {
  @State private var preferences = APIPreferencesLoader.load()

  var body: some View {
    VStack {
      TextField("API Key", text: $preferences.apiKey)
      TextField("baseURL", text: $preferences.baseURL)
      Button("Update", action: {
        APIPreferencesLoader.write(preferences: self.preferences)
      })
    }
  }
}

If you enter some data in the text fields and press the update button, the data you entered will be persisted in a property list that's written to the document directory. When you run this example on your device or in the simulator you will find that your data is now persisted across launches.

In Summary

Because Swift contains a PropertyListEncoder and a PropertyListDecoder object, it's fairly simple to create models that can be written to a property list in Swift. This is especially true if you're already familiar with Codable and the FileManager utility. If you're not very experienced with these technologies, I hope that this Quick Tip provided you with some inspiration and ideas of what to look for, and what to explore.

If you have any feedback about this tip, or if you want to reach out to me don't hesitate to send me a tweet!

Using Result in Swift 5

As soon as Swift was introduced, people were adding their own extensions and patterns to the language. One of the more common patterns was the usage of a Result object. This object took on a shape similar to Swift's Optional, and it was used to express a return type that could either be a success or a failure. It took some time, but in Swift 5.0 the core team finally decided that it was time to adopt this common pattern that was already used in many applications and to make it a part of the Swift standard library. By doing this, the Swift team formalized what Result looks like, and how it works.

In today's post, my goal is to show you how and when Result is useful, and how you can use it in your own code. By the end of this post, you should be able to refactor your own code to use the Result object, and you should be able to understand how code that returns a Result should be called.

Writing code that uses Swift's Result type

Last week, I wrote about Swift's error throwing capabilities. In that post, you saw how code that throws errors must be called with a special syntax, and that you're forced to handle errors. Code that returns a Result is both very different yet similar to code that throws at the same time.

It's different in the sense that you can call code that returns a Result without special syntax. They're similar in the sense that it's hard to ignore errors coming from a Result. Another major difference is how each is used in an asynchronous environment.

If your code runs asynchronously, you can't just throw an error and force the initiator of the asynchronous work to handle this error. Consider the following non-functional example:

func loadData(from url: URL, completion: (Data?) -> Void) throws {
  URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
      throw error
    }

    if let data = data {
      completion(data)
    }
  }.resume()
}

Other than the fact that Swift won't compile this code because the closure that's passed as the data task's completion handler isn't marked as throwing, this code doesn't make a ton of sense. Let's examine what the call site for this code would potentially look like:

do {
  try loadData(from: aURL) { data in
    print("fetched data")
  }

  print("This will be executed before any data is fetched from the network.")
} catch {
  print(error)
}

This isn't useful at all. The idea of using try and throwing errors is that the code in the do block immediately moves to the catch when an error occurs. Not that all code in the do is executed before any errors are thrown, because the data task in loadData(from:completion:) runs asynchronously. In reality, the error that's potentially thrown in the data task's completion handler never actually makes it out of the completion handler's scope. So to summarize this paragraph, it's safe to say that errors thrown in an asynchronous environment never make it to the call-site.

Because of this, Swift's error throwing doesn't lend itself very well for asynchronous work. Luckily, that's exactly where Swift's Result type shines.

A Result in Swift is an enum with a success and failure case. Each has an associated value that will hold either the success value or an error if the result is a failure. Let's look at Result's definition real quick:

/// A value that represents either a success or a failure, including an
/// associated value in each case.
@frozen
public enum Result<Success, Failure: Error> {
  /// A success, storing a `Success` value.
  case success(Success)

  /// A failure, storing a `Failure` value.
  case failure(Failure)
}

The real definition of Result is much longer because several methods are implemented on this type, but this is the most important part for now.

Let's refactor that data task from before using Result so it compiles and can be used:

func loadData(from url: URL, completion: (Result<Data?, URLError>) -> Void) throws {
  URLSession.shared.dataTask(with: url) { data, response, error in
    if let urlError = error as? URLError {
      completion(.failure(urlError))
    }

    if let data = data {
      completion(.success(data))
    }
  }.resume()
}

loadData(from: aURL) { result in
  // we can use the result here
}

Great, we can now communicate errors in a clean manner to callers of loadData(from:completion:). Because Result is an enum, Result objects are created using dot syntax. The full syntax here would be Result.failure(urlError) and Result.success(data). Because Swift knows that you're calling completion with a Result, you can omit the Result enum.

Because the completion closure in this code takes a single Result argument, we can express the result of our work with a single object. This is convenient because this means that we don't have to check for both failure and success. And we also make it absolutely clear that a failed operation can't also have a success value. The completion closure passed to URLSession.shared.dataTask(with:completionHandler:) is far more ambiguous. Notice how the closure takes three arguments. One Data?, one URLResponse? and an Error?. This means that in theory, all arguments can be nil, and all arguments could be non-nil. In practice, we won't have any data, and no response if we have an error. If we have a response, we should also have data and no error. This can be confusing to users of this code and can be made cleaner with a Result.

If the data task completion handler would take a single argument of type Result<(Data, URLResponse), Error>. It would be very clear what the possible outcomes of a data task are. If we have an error, we don't have data and we don't have a response. If the task completes successfully, the completion handler would receive a result that's guaranteed to have data and a response. It's also guaranteed to not have any errors.

Let's look at one more example expressing the outcome of asynchronous code using Result before I explain how you can use code that provides results with the Result type:

enum ConversionFailure: Error {
  case invalidData
}

func convertToImage(_ data: Data, completionHandler: @escaping (Result<UIImage, ConversionFailure>) -> Void) {
  DispatchQueue.global(qos: .userInitiated).async {
    if let image = UIImage(data: data) {
      completionHandler(.success(image))
    } else {
      completionHandler(.failure(ConversionFailure.invalidData))
    }
  }
}

In this code, I've defined a completion handler that takes Result<UIImage, ConversionFailure> as its single argument. Note that the ConversionFailure enum conforms to Error. All failure cases for Result must conform to this protocol. This code is fairly straightforward. The function I defined takes data and a completion handler. Because converting data to an image might take some time, this work is done off the main thread using DispatchQueue.global(qos: .userInitiated).async. If the data is converted to an image successfully, the completion handler is called with .success(image) to provide the caller with a successful result that wraps the converted image. If the conversion fails, the completion handler is called with .failure(ConversionFailure.invalidData) to inform the caller about the failed image conversion.

Let's see how you could use the convertToImage(_:completionHandler:) function, and how you can extract the success or failure values from a Result.

Calling code that uses Result

Similar to how you need to do a little bit of work to extract a value from an optional, you need to do a little bit of work to extract the success or failure values from a Result. I'll start with showing the simple, verbose way of extracting success and failure from a Result:

let invalidData = "invalid!".data(using: .utf8)!
convertToImage(invalidData) { result in
  switch result {
  case .success(let image):
    print("we have an image!")
  case .failure(let error):
    print("we have an error! \(error)")
  }
}

This example uses a switch and Swift's powerful pattern maching capabilities to check whether result is .success(let image) or .failure(let error). Another way of dealing with a Result is using its built in get method:

let invalidData = "invalid!".data(using: .utf8)!
convertToImage(invalidData) { result in
  do {
    let image = try result.get()
    print("we have an image!")
  } catch {
    print("we have an error \(error)")
  }
}

The get method that's defined of Result is a throwing method. If the result is successful, get() will not throw an error and it simply returns the associated success value. In this case that's an image. If the result isn't success, get() throws an error. The error that's thrown by get() is the associated value of the Result object's .failure case.

Both ways of extracting a value from a Result object have a roughly equal amount of code, but if you're not interested in handling failures, the get() method can be a lot cleaner:

convertToImage(invalidData) { result in
  guard let image = try? result.get() else {
    return
  }

  print("we have an image")
}

If you're not sure what the try keyword is, make sure to check out last week's post where I explain Swift's error throwing capabilities.

In addition to extracting results from Result, you can also map over it to transform a result's success value:

convertToImage(invalidData) { result in
  let newResult = result.map { uiImage in
    return uiImage.cgImage
  }
}

When you use map on a Result, it creates a new Result with a different success type. In this case, success is changed from UIImage to CGImage. It's also possible to change a Result's error:

struct WrappedError: Error {
  let cause: Error
}

convertToImage(invalidData) { result in
  let newResult = result.mapError { conversionFailure in
    return WrappedError(cause: conversionFailure)
  }
}

This example changes the result's error from ConversionError to WrappedError using mapError(_:).

There are several other methods available on Result, but I think this should set you up for the most common usages of Result. That said, I highly recommend looking at the documentation for Result to see what else you can do with it.

Wrapping a throwing function call in a Result type

After I posted my article on working with throwing functions last week, it was pointed out to me by Matt Massicotte that there is a cool way to initialize a Result with a throwing function call with the Result(catching:) initializer of Result. Let's look at an example of how this can be used in a network call:

func loadData(from url: URL, _ completion: @escaping (Result<MyModel, Error>) -> Void) {
  URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data = data else {
      if let error = error {
        completion(.failure(error))
        return
      }

      fatalError("Data and error should never both be nil")
    }

    let decoder = JSONDecoder()

    let result = Result(catching: {
      try decoder.decode(MyModel.self, from: data)
    })

    completion(result)
  }
}

The Result(catching:) initializer takes a closure. Any errors that are thrown within that closure are caught and used to create a Result.failure. If no errors are thrown in the closure, the returned object is used to create a Result.success.

In Summary

In this week's post, you learned how you can write asynchronous code that exposes its result through a single, convenient type called Result. You saw how using Result in a completion handler is clearer and nicer than a completion handler that takes several optional arguments to express error and success values, and you saw how you can invoke your completion handlers with Result types. You also learned that Result types have two generic associated types. One for the failure case, and one for the success case.

You also saw how you can call out to code that exposes its result through a Result type, and you learned how you can extract and transform both the success and the failure cases of a Result.

If you have any feedback or questions for me about this post or any of my other posts, don't hesitate to send me a Tweet.

Using KeyPaths as functions in Swift 5.2

One of Swift 5.2's new features is the ability to use KeyPaths as functions. This can be extremely useful in cases where you'd only return the value of a certain KeyPath in a closure. Let's look at a pre-Swift 5.2 example where this is the case:

// Swift 5.1 and earlier
struct User {
  let id: UUID
  let name: String
  let age: Int
}

func extractUserIds(from users: [User]) -> [UUID] {
  return users.map { $0.id }
}

This code should look familiar to you. It's likely that you've written or seen something like this before. This code transforms an array of User objects into an array of UUID objects using map(_:) by returning $0.id for every user in the array.

In Swift 5.2, it's possible to achieve the same with the following code;

func extractUserIds(from users: [User]) -> [UUID] {
  return users.map(\.id)
}

This is much cleaner, and just as clear. We want to map over the user array with the \.id KeyPath which means that we'll end up with an array of UUID. Just like we did before.

You can use this Swift feature in all places where a single-argument closure is used, and where the KeyPath's return type matches that of the closure. We could write the following code to filter an array of users and extract only users that are 21 and up:

extension User {
  var isTwentyOneOrOlder: Bool {
    return age >= 21
  }
}

func extractUsers(_ users: [User]) -> [User] {
  return users.filter(\.isTwentyOneOrOlder)
}

Swift's filter(_:) takes a closure that accepts a single argument (in this case User), and the closure must return a Bool. So by adding a computed property to User that is a Bool, we can use its KeyPath to filter the array of users. Pretty neat, right?

Tip:
Other Swift 5.2 features that I wrote about are callAsFunction and the ability to pass default values to subscript arguments.

Using try catch in Swift

In Swift 2.0, Apple introduced the throws keyword in Swift. This addition to Swift language added the ability for developers to write code that clearly communicates that an error might occur during the execution of a code path, and it forces the caller of that code to handle, or explicitly ignore the error in-place. In this post I will show you what the throws keyword is exactly, and how you can deal with errors in your codebase.

Working with code that throws errors

If you've worked with JSONDecoder in Swift, you have already experienced code that can throw an error. Let's look at a quick example to refresh your mind.

do {
  let data = "hello, world".data(using: .utf8)!
  let decoder = JSONDecoder()
  let string = try decoder.decode(String.self, from: data)
} catch { error in
  print("something went wrong!")
  print(error)
}

This code is a very basic example of how you can work with code that might throw an error. When you call a function that can throw an error, you must prefix this invocation with the try keyword. You can't just do this anywhere. Calling out to code that might throw an error must occur in a do {} catch {} block. The try prefixed code must be in the do portion of the block. You can have more than one try prefixed method call in a single do block. When any of those method calls throws an error, execution is immediately moved to the catch part.

The catch block receives a single argument, which is the Error that was thrown in your do block. In Swift, Error is a protocol that is used to represent errors. It's possible to specialize your catch block to make it catch only a specific kind of error. If you do this, you still need a general purpose catch that will catch all other Error types. Let's look at an example:

do {
  let data = "hello, world".data(using: .utf8)!
  let decoder = JSONDecoder()
  let string = try decoder.decode(String.self, from: data)
} catch is DecodingError {
  print("something went wrong while decoding!")
} catch { error
  print("something went wrong!")
  print(error)
}

You can use pattern matching to specialize catch clauses for an entire category of errors, or a specific error (catch is MyError.someCase). Doing this can be convenient if you have special handling paths for certain errors, but you do lose access to the original error. The final catch-all is invoked if none of the specialized catch blocks match the thrown error.

There are cases when you might make the decision that you don't want to handle any thrown errors. In these cases, you can use the try? prefix instead of try. When you call a throwing function with try?, the function's return type becomes an Optional that's nil when an error was thrown. Let's look at an example:

enum MyError: Error {
  case myErrorCase
}

func doSomething() throws {
  throw MyError.myErrorCase
}

print(try? doSomething())

This example is pretty worthless in a real codebase, but it illustrates my point nicely. The doSomething() function doesn't return anything. This means that it returns Void, which can also be written as an empty tuple (()). The printed result of this code is nil. If you comment out the throw line from doSomething(), the printed output is Optiona(()). In other words, an optional with Void as its value. If a function has a return value and you call it with try?, the result is also optional. Let's look at another example:

let data = "hello, world".data(using: .utf8)!
let decoder = JSONDecoder()
let string = try? decoder.decode(String.self, from: data)

print(string)

If you run this code in a Playground, you'll find that the printed value is nil. The provided data isn't valid JSON, so it fails to decode. But because we call decoder.decode(_:from:) with try?, the error is hidden and decode(_:from:) returns an optional value instead of its normal non-optional value.

If you call a throwing function that returns an optional with try?, you might expect a nested optional to be returned. After all, the function itself returns an optional and the return type of a throwing function is wrapped by an optional when you call it with try?. This means that a function that returns String returns String? when you call it with try?. However, if you call a function that returns String?, it doesn't return String??. Swift will automatically work around this nested optional, which is both a blessing and a curse. Consider the following code:

let string = try? returnsOptionalString()

if string == nil {
  // why is string nil? Is it due to an error? Or did the function execute successfully and we just got back nil without encountering any errors?
}

While the code above might be more convenient to write than a do {} catch {} block, you lose all error-related information. And in cases where code returns an optional, you don't know whether you received nil because of an error. You should only use try? if you truly don't care about handling errors, or knowing whether an error occurred at all.

There is one more way to deal with code that can throw errors. You can use try! in cases where you're absolutely sure that your code shouldn't throw an error, and you want your app to crash if it does. This flavor of try should be used sparingly, and preferably not at all. Let's look at one last example:

enum MyError: Error {
  case myErrorCase
}

func doSomething() throws {
  throw MyError.myErrorCase
}

try! doSomething() // 

This code would crash at runtime. doSomething() always throws an error, and by calling it with the try! prefix we tell Swift that we don't expect doSomething() to actually throw an error. And when it does, execution of the program should halt, and the app should crash. This is quite radical and, again, should be used sparingly.

Throwing errors in your own code

Sometimes, the code you write needs a way to express that something went wrong and execution of that code path needs to stop immediately with an error. If the error is recoverable, you might have a good candidate for a throwing function on your hands. When I say that an error is recoverable, I mean that the error didn't occur due to a programming error (like accessing an out of bounds index in an array for example) and that the program isn't in an unexpected or invalid state. It might simply mean that something went wrong.

For example, when you try to decode invalid JSON using a JSONDecoder, that's not considered an error that's severe enough to crash the app. Instead, an error is thrown to let you know that something went wrong. This is an important distinction, and trying to decode invalid JSON should never crash the application. At least not in the JSONDecoder. You're free to crash your app is a decoding error occurs if you want but I'd strongly advise you not to. Especially if you're loading JSON from a webserver.

When you're writing your own code, you might want to throw an error of your own. You already saw how to do this in the previous section in the doSomething function:

func doSomething() throws {
  throw MyError.myErrorCase
}

Functions that can throw an error must have the throws keyword appended to their signature, before the return type. Here's what a function signature for a throwing function with a return type looks like:

func returnsOptionalString() throws -> String? {
  // do work
}

When you're writing code in a so-called throwing function, you can call methods that throw errors without using a do {} catch {} block:

func decodeJSON(_ data: Data) throws -> String {
  let decoder = JSONDecoder()
  let decodedString = try decoder.decode(String.self, from: data)

  return decodedString
}

This code is okay because Swift knows that decodeJSON(_:) might encounter and throw an error. When the JSON decoding fails in this function, the thrown error is passed up to the caller of decodeJSON because it's marked as throwing with the throws keyword. When this function is called from another function that's throwing, that function will also forward the error up to its caller. The error will be passed up all the way to a caller that's not part of a throwing function.

There is one more error throwing related keyword that I want to show you. It's called rethrows. The rethrows keyword is used for functions that don't directly throw an error. Instead, the functions take a closure argument where the closure might throw instead of the function itself. Let's look at an example of a function that takes a throwing closure without rethrows:

func execute(_ closure: (() throws -> Void)) throws {
  try closure()
}

do {
  try execute {
    print("hello!")
  }

  try execute { 
    throw MyError.myErrorCase
  }
} catch {
  print(error)
}

In the code above I have defined an execute function. This function takes a single closure argument, and all it does is execute the closure immediately. Nothing fancy. You'll see that both execute(_:) and the closure it receives are marked with throws. It's important to understand that marking a function as throwing does not mean that the function is guaranteed to throw an error. All we're saying is that it might. This is especially relevant for the closure argument. The closure that's passed might not even be capable of throwing an error, just like the first call to execute(_:) in this example. Even though we know that this closure never throws an error, and the compiler also knows it, we must mark the call to execute(_:) with try because that function itself might throw an error.

We can clean this code up a little bit by declaring execute(_:) as rethrowing rather than throwing:

func execute(_ closure: (() throws -> Void)) rethrows {
  try closure()
}

execute {
  print("hello!")
}

do {
  try execute {
    throw MyError.myErrorCase
  }
} catch {
  print(error)
}

Because execute(_:) is now rethrowing, the Swift compiler can verify whether a code path might throw an error, and it will allow you to call execute(_:) without try if it can prove that the closure you passed to execute(_:) doesn't throw an error. Quite neat right?

You'll find rethrows in several places in the Swift standard library. For example, map(_:) is marked with rethrows because the closure you supply to map(_:) is allowed to throw errors if needed:

let mapped: [Int] = try [1, 2, 3].map { int in
  if int > 3 {
    throw MyError.intLargerThanThree
  }

  return int * 2
}

This probably isn't how you commonly use map(_:) because typically the closure passed to this function doesn't throw. But now you know that you're allowed to throw errors while mapping, and you also know why you're not forced to mark every call to map(_:) with try.

Note that the execution of any throwing method or closure is halted immediately when an error is thrown:

func doSomething() throws {
  throw MyError.myErrorCase

  print("This is never printed")
}

Throwing an error is a strong signal that something's wrong, and there's no point in fully executing the current code path. The error is sent to the caller of the throwing function, and it must be handled or forwarded from there. If you throw an error in a function that should return something, the function will not actually return anything. Instead, your code switches to the catch part of the do {} catch {} block immediately where you can handle the error. The exception here is when the called of your throwing function calls it with try? or try! like I explained in the previous section.

In Summary

In this post, I've shown you how you can deal with functions that can throw errors in Swift. You saw how you can call functions that are marked as throwing, how you can tell Swift you're not interested in handling an error and how you can tell Swift that you're absolutely sure a certain call will never actually throw an error at runtime.

After that, I moved on to show you how you can throw errors from your own code, what the rethrows keyword is and when it's useful.

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

Adding default values to subscript arguments in Swift 5.2

The ability to define custom subscripts in Swift is really powerful. It allows us to write very natural and concise code. Consider the following example of a Grid with a custom subscript:

struct Grid {
  let items : [[GridItem]]

  subscript(x x: Int, y y: Int) -> GridItem? {
    guard !items.isEmpty, (items.startIndex...items.index(before: items.endIndex)).contains(x)
      else { return nil }

    let row = items[x]

    guard !row.isEmpty, (row.startIndex...row.index(before: row.endIndex)).contains(y)
      else { return nil }

    return row[y]
  }
}

Note that subscripts don't use labels by default. To make a subscript use labels, you need to manually declare the subscript label twice like I did in the code above (x x: Int and y y: Int). A subscript like the above would be used as follows:

let item = grid[x: 10, y: 2]

This example works fine in Swift 5.1 and earlier versions in Swift. New in Swift 5.2 is the ability to provide default values for subscript arguments:

struct Grid {
  let items : [[GridItem]]

  subscript(x x: Int = 0, y y: Int = 0) -> GridItem? {
    guard !items.isEmpty, (items.startIndex...items.index(before: items.endIndex)).contains(x)
      else { return nil }

    let row = items[x]

    guard !row.isEmpty, (row.startIndex...row.index(before: row.endIndex)).contains(y)
      else { return nil }

    return row[y]
  }
}

This code is almost the same as the first example, except the subscript now has default arguments. This means that you can use the subscript as follows:

let item = grid[y: 2]

This will automatically call the subscript with a default value of 0 for the x argument.

The fact that it wasn't possible to define default subscript arguments in Swift before was kind of a surprise for me to be honest, but I'm really happy that we have the possibility now. I think it will allow many people to write better subscripts for their objects.

How and when to use callAsFunction in Swift 5.2

A new Swift 5.2 feature is the ability to call instances of types as functions. Or, as the Swift Evolution proposal calls it "Callable values of user-defined nominal types". The very short description of this feature is that it allows you to call instances of any type that has a callAsFunction method implemented as if it's a function:

struct InvestmentsCalculator {
  let input: Double
  let averageGrowthPerYear = 0.07

  func callAsFunction(years: Int) -> Double {
    return (0..<years).reduce(input, { value, _ in
      return value * (1 + averageGrowthPerYear)
    })
  }
}

let calculator = InvestmentsCalculator(input: 1000)
let newValue = calculator(years: 10)

While this is pretty cool, you might wonder when a feature like this is useful in the real world, and how you can apply it in your code. Luckily, that's exactly what I hope to help you discover in today's post, and what I've been trying to find out myself.

Understanding the background of callAsFunction

Swift isn't the only language to allow its users to call instances of certain types as functions. A language that I have used a lot that allows this kind of behavior is Python. The ability to invoke instances as functions is very interesting in certain applications where you want to represent a stateful calculator, parser or computing object. This is very common in complex math operations and machine learning where certain objects might hold on to some state and only implement a single method.

An application that I am more familiar with myself, is to pass a renderer object to a function in Python. I won't bother you with Python code in this post, but I do want to show you what I mean by my previous statement. So let's look at a Swift example:

protocol Route {
  associatedtype Output
}

func registerHandler<R: Route>(_ route: R, _ handler: (R) -> R.Output) {
  return renderer(route)
}

Upon first glance, this shouldn't look too crazy. It's a function that accepts an object that conforms to Route, and a closure that takes this Route as its argument, and returns R.Output, whatever that may be. If you were to call this function, you might write the following:

registerHandler(homeRoute) { route in
  /* Do a bunch of work to create output */

  return output
}

This might work fine for very simple renderers. However, in the context of Python I mentioned before, this kind of code would be running on a web server. And a route would be the equivalent of a URL or path to a webpage. The closure passed to registerHandler is called whenever the associated route is requested by the user. In many cases, a simple closure wouldn't do. The object that ends up handling the route would need to have a database connection, a concept of caching, authenticating and possibly a lot of other functionality. Capturing all of that in a closure just doesn't seem like a great idea. Instead, you'd want some kind of complex object to handle this route. And that's exactly the kind of freedom we get with callAsFunction:

struct HomeHandler {
  let database: Database
  let authenticator: Authenticator

  // etc...

  func callAsFunction<R: Route>(_ route: R) -> R.Output {
    /* Do a bunch of work to create output */

    return output
  }
}

let homeHander = HomeHandler(database: database, authenticator: authenticator)

registerHandler(for: homeRoute, homeHandler)

Patterns like this are quite common in Python, and in my opinion, it's pretty cool to be able to transparently pass entire objects to functions take closures is really neat.

If you know a thing or two about closures, you might be wondering if there's any difference between the previous code, and the following:

registerHandler(for: homeRoute, homeHandler.callAsFunction)

And there really isn't much of a difference other than the amount of typing you need to do. Functions can still be passed to functions that take closures just fine. It just reads a little bit nicer to not have to specify what function on an object needs to be called exactly.

Understanding how to use callAsFunction

Using callAsFunction in Swift is relatively straightforward. Any object that defines a callAsFunction method can be treated as a function. Your callAsFunction can take arguments and return values as shown in the Swift Evolution proposal with the following example:

struct Adder {
  let base: Int

  func callAsFunction(_ x: Int) -> Int {
    return base + x
  }
}

let add3 = Adder(base: 3)
add3(10) // 13

Or it can take no arguments at all:

struct Randomizer<T> {
  let elements: [T]

  func callAsFunction() -> T? {
    return elements.randomElement()
  }
}

let randomizer = Randomizer(elements: [1, 2, 3])
randomizer() // a random element

And you can even have multiple overloads on a single object:

struct Randomizer<T> {
  let elements: [T]

  func callAsFunction() -> T? {
    return elements.randomElement()
  }

  func callAsFunction(resultCount: Int) -> [T] {
    return (0..<resultCount).reduce(into: [T]()) { result, _ in
      if let element = elements.randomElement() {
        result.append(element)
      }
    }
  }
}

let randomizer = Randomizer(elements: [1, 2, 3])
randomizer() // a random element
randomizer(resultCount: 2) // an array with two random elements

The ability to decide whether you want your callAsFunction implementation to take arguments and what its return type is, makes it a pretty powerful feature. You can really customize this feature to your heart's content and because you can add multiple overloads of callAsFunction to your objects, you can use a single object as a function in many contexts.

In Summary

The ability to make your types callable might not be something that you're likely to use a lot. It's a niche feature that has a couple of very specific, yet convenient, applications. In today's post, you saw an example of how Python uses callables in a framework that I've worked with a couple of years ago to build websites. I roughly translated the ability to register route handlers using objects to Swift to give you an idea of what a practical application could look like and give you a little bit of background from a new perspective.

I then proceeded to show you how callAsFunction can be used on your own types. You saw that it's totally up to you to decide whether you want your implementation to take arguments and that you get to pick the return type of callAsFunction yourself. You also learned that it's possible to specify your overloads for callAsFunction which means that you can have multiple flavors of callAsFunction on a single type.

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

Testing push notifications in the Simulator with Xcode 11.4

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

Sending a test push from the command line

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

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

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

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

xcrun simctl push booted test_push.apns

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

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

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

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

Sending a test push notification without the command line

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

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

In Summary

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

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

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

Using Promises and Futures in Combine

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

In this post I will cover the following topics:

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

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

Understanding what Promises and Futures are

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let future = createFuture()  // nothing happens yet

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

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

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

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

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

Wrapping an existing asynchronous operation in a Future

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In summary

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

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

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

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

Five ways to get better at receiving feedback

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

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

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

1. Listen, don't defend

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

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

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

2. Let the feedback sink in

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

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

3. Don't take it personally

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

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

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

4. Ask for more information if needed

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

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

5. Ask for help

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

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

In summary

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

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

Using map, flatMap and compactMap in Combine

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

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

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

Transforming a publisher's output with map

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

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

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

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

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

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

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

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

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

Transforming values with compactMap

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

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

This code has the following output:

Received 1
Received 2
Received 3
Received 4
Received 5

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

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

Transforming values with flatMap

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

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

Would be flatMapped into the following:

[1, 2, 3, 4]

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

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

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

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

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

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

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

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

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

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

In summary

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

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