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.
Create
delegate/protocol
with your Location class- Create one protocol and implement that protocol method with your
ViewController
and declare its instance in yourLocation
class. After then in thecompletionHandler
ofreverseGeocodeLocation
call this delegate method. Check Apple documentation onProtocol
for more details.
- Create one protocol and implement that protocol method with your
You can create
completionHandler
with yourgetLocationName
method ofLocation
class.Add
completionHandler
withgetLocationName
and called thatcompletionHandler
inside thecompletionHandler
ofreverseGeocodeLocation
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 yourupdateUI
method inside the completion block.Location.sharedInstance.getLocationName { (success) in
if success {//If successfully got response
self.updateUI()
}
}
You can add observer for
(NS)NotificationCenter
.- Register the observer with
(NS)NotificationCenter
and then post the notification inside thecompletionHandler
ofreverseGeocodeLocation
. You can get more detail on this with this StackOverflow Post.
- Register the observer with
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
Connect Physicsbodies on Tilemap in Spritekit
Audiokit Seems to Receive Only The First Three Numbers of Sysex Midi Messages
Spacing Between Sections in a Form
Populate a Multidimensional Array with a Loop
Not Getting Screenlock Notification on Swift 4 on Mac
Tapping Is Required Twice to Uncheck Table Cell
Thread Safety of Method Calls on "Shared" Static Constant Property
Uicollectionview + Nsfetchedresultscontroller in Swift 3
Given a Swift 'Any' Type How to Determine If It's an 'Optional'
Access Environment Variable Inside Global Function - Swiftui + Coredata
Cannot Increment Beyond Endindex
Swift - Rotate Gesture and Rotation Increments of 90 Degrees
How to Make an Ellipse/Circular UIimage with Transparent Background
Outlets Cannot Be Connected to Repeating Content