Wak Wak - 6 months ago 22
Swift Question

Swift completion block

I'm having a hard time understanding a problem I'm having.
To simplify, I'll use UIView method.
Basically, if I write the method

UIView.animateWithDuration(1, animations: {() in
}, completion:{(Bool) in
println("test")
})


it works fine.
Now, if I do the same method, but creating a string like so:

UIView.animateWithDuration(1, animations: {() in
}, completion:{(Bool) in
String(23)
})


It stops working. Compiler error: Missing argument for parameter 'delay' in call

Now, here's the strange part. If I do the exact same code as the one that fails, but just add a print command like so:

UIView.animateWithDuration(1, animations: {() in
}, completion:{(Bool) in
String(23)
println("test")
})


it starts to work again.

My problem is basically the same thing. My code:

downloadImage(filePath, url: url) { () -> Void in
self.delegate?.imageDownloader(self, posterPath: posterPath)
}


Doesn't work. But if I change to.

downloadImage(filePath, url: url) { () -> Void in
self.delegate?.imageDownloader(self, posterPath: posterPath)
println("test")
}


or even:

downloadImage(filePath, url: url) { () -> Void in
self.delegate?.imageDownloader(self, posterPath: posterPath)
self.delegate?.imageDownloader(self, posterPath: posterPath)
}


It works fine.
I can't understand why this is happening. I'm close to accept that it's a compiler bug.

Answer

Closures in Swift have implicit returns when they are only made up of a single expression. This allow for succinct code such as this:

reversed = sorted(names, { s1, s2 in s1 > s2 } )

In your case, when you create your string here:

UIView.animateWithDuration(1, animations:  {() in }, completion:{(Bool) in
    String(23)
})

you end up returning that string and that makes the signature of your closure:

(Bool) -> String

That no longer matches what's required by animateWithDuration's signature (which translates to Swift's cryptic Missing argument for parameter 'delay' in call error because it can't find an appropriate signature to match).

An easy fix is to add an empty return statement at the end of your closure:

UIView.animateWithDuration(1, animations:  {() in}, completion:{(Bool) in
    String(23)
    return
})

Which makes your signature what it should be:

(Bool) -> ()

Your last example:

downloadImage(filePath, url: url) { () -> Void in
    self.delegate?.imageDownloader(self, posterPath: posterPath)
    self.delegate?.imageDownloader(self, posterPath: posterPath)
}

works because there are two expressions there, not just one; implicit returns only happen when the closure contains a single expression. So, that closure isn't returning anything and that matches its signature.