Calling Closure Inside Closure

Calling closure inside closure

The type of your work closure needed work:

var work: ((Int, _ completionHandler: () -> ()) -> ())?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

// Use optional chaining here to unwrap work before calling
work?(1, {
// This now works.
})

workAsMethod(amount: 1) {
// Works.
}

work = { (amount, completionHandler) in
// This now works
completionHandler()
}

return true
}

func workAsMethod(amount: Int, completionHandler: @escaping (() -> ())) {
// Works
completionHandler()
}

work is an optional closure that takes an Int and a () -> () and it returns nothing -> ().

If you break down your type for work, your completionHandler has type (() -> ()) -> () which means your completionHandler takes a () -> () closure and returns nothing. You want your completerHandler to take no parameters. Because of the misplaced ( and ), your work was actually an optional tuple and not a closure type.

How to call closure with closure as argument

I tried Arc<dyn Fn(dyn FnOnce(&mut [u8]), usize) -> Result<(), ()> + Send + Sync>

That should be &dyn FnOnce(...), but that won't work either because calling FnOnce automatically moves it, so it can't be called from behind a reference. The simplest solution is to introduce an extra allocation in consume, because Box<dyn FnOnce> implements FnOnce itself since Rust 1.35. For example (playground):

pub type OnVirtualTunWrite = Arc<
dyn Fn(Box<dyn FnOnce(&mut [u8])>, usize) -> Result<(), VirtualTunWriteError> + Send + Sync,
>;

pub struct A {
pub on_virtual_tun_write: OnVirtualTunWrite,
}

impl A {
pub fn consume<F>(&self, f: F)
where
F: FnOnce(&mut [u8]) + 'static,
{
(self.on_virtual_tun_write)(Box::new(f), 0).unwrap();
}
}

To avoid the allocation, you can use the technique described here to invoke FnOnce from an FnMut. It uses Option rather than Box, so it's zero-cost, or at least allocation-free. For example (full code in the playground):

pub type OnVirtualTunWrite = Arc<
dyn Fn(&mut dyn FnMut(&mut [u8]), usize) -> Result<(), VirtualTunWriteError> + Send + Sync,
>;

trait CallOnceSafe {
fn call_once_safe(&mut self, x: &mut [u8]);
}

impl<F: FnOnce(&mut [u8])> CallOnceSafe for Option<F> {
fn call_once_safe(&mut self, x: &mut [u8]) {
// panics if called more than once - but A::consume() calls it
// only once
let func = self.take().unwrap();
func(x)
}
}

impl A {
pub fn consume<F>(&self, f: F)
where
F: FnOnce(&mut [u8]) + 'static,
{
let mut f = Some(f);
(self.on_virtual_tun_write)(&mut move |x| f.call_once_safe(x), 0).unwrap();
}
}

The above works by first moving the FnMut into an Option, and calling it through call_once_safe, a method on a private CallOnceSafe trait with a blanket impl for Option<T: FnOnce(...)>. The blanket implementation moves the closure out of the Option and invokes it. This is allowed because the size of the closure is known in the blanket implementation, which is generic over T.

The blanket impl can get away with mutating rather than consuming self because it uses Option::take to move the content out of the option, while leaving it empty and otherwise usable. Not consuming the option allows call_once_safe to be called from an FnMut closure, such as the one created in consume and passed as argument to OnVirtualTunWrite. call_once_safe does consume the actual FnOnce closure contained in the Option, thus preserving the invariant that the closure is called no more than once. If consume were to call call_once_safe on the same Option<F> twice (or if the outer closure called its first argument more than once), it would result in a panic.

How To Call a func within a Closure

To handle this situation you have multiple option.

  1. Create delegate/protocol with your Location class

    • Create one protocol and implement that protocol method with your ViewController and declare its instance in your Location class. After then in the completionHandler of reverseGeocodeLocation call this delegate method. Check Apple documentation on Protocol for more details.
  2. You can create completionHandler with your getLocationName method of Location class.

    • Add completionHandler with getLocationName and called that completionHandler inside the completionHandler of reverseGeocodeLocation like this way.

      func getLocationName(completionHandler: @escaping (_ success: Bool) -> Void) {

      let geoCoder = CLGeocoder()
      let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)

      geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
      guard let addressDict = placemarks?[0].addressDictionary else {
      completionHandler(false)
      return
      }
      if let city = addressDict["City"] as? String {
      self.currentCity = city
      print(city)
      }
      if let zip = addressDict["ZIP"] as? String {
      print(zip)
      }
      if let country = addressDict["Country"] as? String {
      print(country)
      }
      completionHandler(true)
      //self.nowUpdateUI()
      })
      }

      Now in ViewController where you are calling this function call your updateUI method inside the completion block.

      Location.sharedInstance.getLocationName { (success) in
      if success {//If successfully got response
      self.updateUI()
      }
      }
  3. You can add observer for (NS)NotificationCenter.

    • Register the observer with (NS)NotificationCenter and then post the notification inside the completionHandler of reverseGeocodeLocation. You can get more detail on this with this StackOverflow Post.

MissingMethodException while calling Groovy closure inside closure

The problem with the original code is, that you are defining just some
local variables, that never get used. Instead the assigned closure is
returned directly.

To make your original code work, you can call:

closure()("A")("B")

(Each closure call returns the next closure and you just chain the
calls; of course there is no need to have the def clouserXXX in
there).

If you leave the only def out, you will create "global vars" and that is
most likely not what you want.

If you want to have the names in there, you have to return something
with the names. One simple example is using maps as the return. E.g.:

def closure = { ->
[closureOne: { _argsA ->
[closureTwo: { _argsB ->
println(_argsA)
println(_argsB)
}]
}]
}

closure().closureOne("A").closureTwo("B")

Swift closure with parameter syntax

Also, shouldn't there be a way to write one sip() method with variable parameters so that it can take beer OR gin?

Right now, beer and gin are different types of closures (beer takes a parameter while gin doesn't), which is why you need two overloads of sip - one to handle each type. Imagine what would happen if there were a single sip that could accept both of them. How would it know whether to pass a parameter or not?

On the other hand, if your ultimate goal is something like this:

sip(on: beer(brew.porter))

Then declaring a single sip is possible.

Right now, doing beer(brew.porter) will call the beer closure. The beer closure returns Void, and Void can't be passed into sip, so that won't work. Your intuition might say "Why can't beer(brew.porter) return another closure, that, when called with no parameters, drinks a beer with a porter brew?" And congrats, you have discovered currying.

You can change beer to do exactly that:

let beer = { (kind: Brew) -> (() -> Void) in // a closure that returns a closure!
// return { () -> Void in print("\(kind), smooth!") }
return { print( "\(kind), smooth!") } // short-hand
}

And now you only need this sip:

func sip( on drink: () -> Void ) {
print("Sip...")
drink()
}

In the real world though, you often can't change beer (because it's someone else's code, or it doesn't make sense). In that case, you must change how you call sip:

// "{}" indicates a closure! You are putting the closure call "beer(brew.porter)" inside another closure.
sip(on: { beer(brew.porter) })


Related Topics



Leave a reply



Submit