Jaseem Abbas Jaseem Abbas - 2 months ago 13
Swift Question

What does () mean in Swift?

I have the following function in

Swift 3


func fetchOrders(_ completionHandler: (_ orders: [Order]) -> Void)
{
ordersStore.fetchOrders { (orders: () throws -> [Order]) -> Void in
do {
let orders = try orders()
completionHandler(orders)
} catch {
completionHandler([])
}
}
}



  1. What does
    _ completionHandler
    argument in
    fetchOrders
    mean?

  2. What does
    (orders: () throws -> [Order])
    mean?



PS : I am new to
iOS
and
Swift

Answer

There's quite a lot in here, so we'll break it down one piece at a time:

func fetchOrders(_ completionHandler: (_ orders: [Order]) -> Void)
  • This is a function called fetchOrders.
  • It has one parameter (completionHandler) and returns nothing.
  • The first _ indicates that there is no "external name" of the first parameter. That is, you do not have to label it (in fact, you cannot). (For subtle reasons that don't really matter here, I believe the author made a mistake using _ there, and I would not have done that.)
  • The completionHandler is the "internal name," what the parameter is called inside the function.
  • The type of completionHandler is (_ orders: [Order]) -> Void. We'll break that down now.
    • This value is a closure that takes an [Order] (array of Order) and returns Void. Informally this means "returns nothing" but literally means it returns the empty tuple ().
    • The _ orders: syntax is in practice a comment. In principle the _ is an external name (but that's the only legal external name for a closure), and orders is an internal name, but in reality, closures parameters do not have names in any meaningful way, so this is purely informational.
    • I believe this is a poor use of the closure parameter commenting system. Since orders tells us nothing more than [Order], I would have omitted it, and made the type just ([Order]) -> Void.

Now we'll turn to the next line:

ordersStore.fetchOrders { (orders: () throws -> [Order]) -> Void in
  • This calls the fetchOrders method on ordersStore. We can tell from this code that fetchOrders takes a closure parameter. This is called "trailing closure" syntax in Swift, and is why I would not have used the _ for our closure. With trailing closure syntax, the external name of the parameter is not needed.
  • The author has provided type information here that probably wasn't necessary, but we can explore it anyway. This could likely have been written as just { orders in, but then the reader would probably have been surprised by this somewhat unusual code.
    • We have been passed a closure called orders that takes nothing and returns [Order] or throws an error. Basically this is a way to say that fetchOrders might fail.
    • The author is working around an awkwardness in Swift's throws system, which does not have a natural way to express an asynchronous action that might fail. This is one way to fix it; you pass a throwing (i.e. a possibly failing) function. I don't favor this approach, I favor using a Result enum for this case because I think it scales better and avoids possible unintended side effects, but that's a debatable point (and the Swift community hasn't really decided how to deal with this common problem).

This all leads us to:

  do {
    let orders = try orders()
    completionHandler(orders)
  } catch {
    completionHandler([])
  }
  • This is where the orders closure is evaluated. (This is very important; if orders has side effects, this is when they occur, which may be on a different queue than was intended. That's one reason I don't favor this pattern.) If the closure succeeds, we return its result, otherwise we return [] in the catch below.
    • In this particular case, the throws approach is slightly silly, because it's silently flattened into [] without even a log message. If we don't care about the errors, then failure should have just returned [] to start with and not messed with throws. But it's possible that other callers do check the errors.
  • In either case, we call the completionHandler closure with our result, chaining this back to our original caller.

This do/catch block could have been more simply written as:

let completedOrders = try? orders() ?? []
completionHandler(completedOrders)

This makes it clearer that we're ignoring errors by turning it into an optional, and avoids code duplication of the call to completionHandler.

(I just add the extra let binding to make the code a little easier to read; it isn't needed.)