3

I have a completion handler that I need to assign to a property, but I want it to execute asynchronously.

If I didn't have that requirement, I would write:

request.completionBlock = completionBlock

But since I have this requirement, I have to write this

request.completionBlock = { response, error in
  DispatchQueue.main.async {
    completionBlock(response, error)
  }
}

which seems redundant and un-swifty.

Isn't there some easier syntax? I would like to write something like

request.completionBlock = completionBlock.map(DispatchQueue.main.async)

Can I express my need in such a simple way?

KPM
  • 10,558
  • 3
  • 45
  • 66
  • Is `request` an instance of a type that you can alter, or does it need to be an extension/subclass of it? – DavidA Nov 16 '16 at 19:35

4 Answers4

4

There isn't a built-in syntax for expressing that, but you can always define a generic function or operator to enable something along those lines.

For example:

infix operator >

func ><T>(left:@escaping (T)->(), right:DispatchQueue) -> (T)->() {
  return { input in
    right.async { left(input) }
  }
}

With the above custom operator defined, your code can be:

request.completionBlock = completionBlock > DispatchQueue.main

which I think is the general feel you are looking for.

KPM
  • 10,558
  • 3
  • 45
  • 66
Daniel Hall
  • 13,457
  • 4
  • 41
  • 37
  • I was precisely just about to suggest to formulate it the other way around :) Thanks! – KPM Nov 24 '16 at 16:52
  • 1
    With `->` I had a syntax error. With just `>` (or `§`, or whatever, everything looks fine). So I updated the answer. – KPM Nov 24 '16 at 17:23
  • And actually we don't need the MultiplicationPrecedence. It is even undesirable for two reasons: 1/ we don't want to have associativity, because it doesn't make sense to write block < queue < queue. And 2/ we are actually overloading an existing operator so we don't want to give it a different precedence rule. – KPM Nov 25 '16 at 15:15
  • 1
    @KPM Good point! The original code example was a custom operator, not an overload of an existing one, but default precedence would have been more appropriate. Or if anything, a lower precedence that would have supported functionally binding a number of closures together in a chain before wrapping the execution in a Dispatch Queue as the last step – Daniel Hall Nov 25 '16 at 16:38
4

Here is an extension

extension DispatchQueue {
    func asyncClosure<T>(_ closure:@escaping (T) -> Void) -> (T) -> Void {
        return {i in self.async {closure(i)}}
    }
}

that allows you to do this:

request.completionBlock = DispatchQueue.main.asyncClosure(completionBlock)
bzz
  • 5,556
  • 24
  • 26
  • This answer was very useful too. I hesitated longly between yours and Daniel Hall's. In the end I chose to award the bounty to Daniel. Thanks a lot for the alternative! – KPM Nov 24 '16 at 16:55
0

Do you have control over the request class? If not, then I think you have to bite the bullet and explicitly dispatch asynchronously yourself (and explicit is good, or at least it is in python :-) ), or define your own shorthand like Daniel Hall suggests.

If you do have control, then I think it's worth suggesting simply changing the API of your request class so that it guarantees the completion handler is called on the main thread. Completion handlers are supposed to be quick, after all, and this is often what you want anyway.

daphtdazz
  • 7,754
  • 34
  • 54
0

I realize this is a rather old question, but I thought I'd post an answer with a much simpler way to accomplish this task.

If you want to have your code wrapped in whatever call you wish, in this case we'll deal specifically with wrapping in a DispatchQueue call, you simply create a function as follows:

func myActionDelay(_ completion: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        completion()
    }
}

To use this just call it as follows:

myActionDelay {
    myBool = false
}

The action of setting myBool to false will take place on the main thread after 0.1 seconds from now.

Of course you can use this same format for any code you wish to repeatedly place inside some other function. Just change the DispatchQueue call to whatever you wish, and you're all set.

Hope this helps!

kittonian
  • 1,020
  • 10
  • 22