0

in my iOS app I am running a HKStatisticsCollectionQuery, which has an

query.initialResultsHandler = { query, results, error in [...] }

and a

query.statisticsUpdateHandler = { query, hkStats, results, error in [...] }

Both closures are exactly the same.

Is there a way to write the code of these closures just once and reference it twice? Usually I would refactor / extract a function and just call that, but here I have a

DispatchQueue.main.async { ... }

inside the handlers so I'm not sure it would work... And as you may infer from my question I'm not exactly sure if I am getting closures and dispatchQueue alright...

Gin Tonyx
  • 383
  • 1
  • 11

2 Answers2

0

Closures and functions are interchangeable. Any place you need to pass a closure, you can pass a function instead.

Thus you could write a function for both of your cases and pass that function name instead of a closure.

That said, your two examples show different parameters, which argues against your statement that "Both closures are exactly the same". If they take different parameters, how can they be the same?

Edit:

Consider the following function:

func foo(value: String, completion: (String)->Void) {
    completion(value)
}

We could invoke foo using a closure, as you'd expect:

foo(value: "firstCall", completion: { value in
    print("in completion handler, value = '\(value)'")
})

That would output

in completion handler, value = 'firstCall'

We could also define one or more functions who's signatures match the completion handler:

func completionFunction(value: String) {
    print("In \(#function), value = '\(value)'")
}

func completionFunctionTwo(value: String) {
    print("In \(#function), value = '\(value)'")
}

And then call those like this:

foo(value: "secondCall", completion: completionFunction)
foo(value: "thirdCall", completion: completionFunctionTwo)

Note how we're just passing a function name as the completion handler. That's equivalent to passing an in-line closure.

Those calls would output:

In completionFunction(value:), value = 'secondCall'

In completionFunctionTwo(value:), value = 'thirdCall'

Finally, you can create a variable that contains a closure, and pass that as your completion handler:

let aCompletionClosure: (String) -> Void = { value in
    print("In completion closure, value = '\(value)'")
}

foo(value: "forthCall", completion: aCompletionClosure)

That outputs:

In completion closure, value = 'forthCall'

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I am not using the "extra" hkStats in the update handler. It's just there because it's in apple's documentation that it's the second property passed to the closure. I still don't know if I could leave it out, but that's a different topic.. maybe later. --- Anyway. thanks a lot for the long answer. I'll have to digest that, maybe takes a while... – Gin Tonyx Jan 20 '21 at 16:24
0

Since your closures have different arguments, you need to create a function, which can be called in both closures:

func handle(query: ...?, results: ...?, error: Error?) {
    //...
}

Since arguments for the first handler are exactly the same as your function, you could simply assign it to the handler:

query.initialResultsHandler = handle

or you could call it from your closure:

query.initialResultsHandler = { query, results, error in 
    self.handle(query: query, results: results, error: error)
}

In the second handler, arguments are different, so you can call it:

query.statisticsUpdateHandler = { query, hkStats, results, error in 
    self.handle(query: query, results: results, error: error)
}

Alternatively you could define a second function, which matches a second closure signature, and call the first function from it:

func handleUpdate(query: ...?, hkStats: ...?, results: ...?, error: Error?) {
    handle(query: query, results; results, error: error)
}

In that case you can also assign that function to the second handler:

query.statisticsUpdateHandler = handleUpdate
timbre timbre
  • 12,648
  • 10
  • 46
  • 77