Within the Swift Foundation Library there exists a function called withoutActuallyEscaping(_:do:) that has, at least for me, become one of those hidden gems that is extremely useful for things beyond its original intention. As it’s documentation states:
Allows a nonescaping closure to temporarily be used as if it were allowed to escape.
Apple Documentation – Swift Standard Library – Type Casting and Existential Types
Where I first used this function was in correcting a bug in the Open Source version of Swift where the definition of the method NSRegularExpression.enumerateMatches(in:options:range:using:) mistakenly included the “@escaping” attribute for the closure. Since that method is synchronous and does not return until it has completed it was pointless for the Open Source version of Swift to contain the “@escaping” attribute – especially since the Apple version does not include it.
So to make the Open Source version and the Apple version of the Foundation libraries agree with each other I simply created an extension and used withoutActuallyEscaping(_:do:) to fix it.
extension NSRegularExpression { func myEnumerateMatches(in string: String, options: MatchingOptions = [], range: NSRange, using block: (NSTextCheckingResult?, MatchingFlags, UnsafeMutablePointer<ObjCBool>) -> Void) { withoutActuallyEscaping(block) { (_block) -> Void in enumerateMatches(in: string, options: options, range: range, using: _block) } } }
But since then I’ve found another use for this function that falls outside of what it was intended for. The other use is to allow closures to become throwable when the original API doesn’t allow for it. Let’s take a look at the same method again – NSRegularExpression.enumerateMatches(in:options:range:using:). This method does not allow for a thrown error inside the closure.
Since I was creating my own version of this method I decided to make it a little more Swift-like in nature. Following the pattern of closures in the Swift Standard Library, I wanted the closure used by this method to take a reference to an actual “Bool” type rather than an UnsafeMutablePointer to an Objective-C boolean value. I also wanted the closure to be able to throw an error and have that error propagated to the calling code. In short, I wanted the signature of the closure and the corresponding method to be:
typealias MatchClosure = (NSTextCheckingResult?, MatchingFlags, inout Bool) throws -> Void func enumerateMatches(in string: String, options: MatchingOptions = [], range: Range<String.Index>, using block: MatchClosure) rethrows
Because the original method’s closure doesn’t allow for thrown errors we have to do our own handling of the error:
import Foundation extension NSRegularExpression { typealias MatchClosure = (NSTextCheckingResult?, MatchingFlags, inout Bool) throws -> Void func enumerateMatches(in string: String, options: MatchingOptions = [], range: Range<String.Index>, using block: MatchClosure) rethrows { var error: Error? = nil let nsRange = NSRange(range, in: string) enumerateMatches(in: string, options: options, range: nsRange) { (results: NSTextCheckingResult?, flags: MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) in var _stop: Bool = false // Trap the error and save it to a variable // outside the closure. do { try block(results, flags, &_stop) } catch let e { // Once we've trapped and saved the error we set // the `_stop` predicate to `true` to exit out of // the enumeration. error = e _stop = true } stop.pointee = ObjCBool(_stop) } // Finally, if we did set an error inside the // closure then we can rethrow it here. if let e = error { throw e } } }
Now, this all looks good, but if you try to compile the code above you’d find that the compiler flags an error at line 35 that says, “A function declared ‘rethrows’ may only throw if its parameter does.“
To fix this we would have to change line 9 from a “rethrows” to a “throws“. But we don’t really want to do that because we only want to have to prefix a call to enumerateMatches with a “try” if the closure actually does throw an error.
So this is where we can use withoutActuallyEscaping(_:do:) to accomplish this.
import Foundation extension NSRegularExpression { typealias MatchClosure = (NSTextCheckingResult?, MatchingFlags, inout Bool) throws -> Void func enumerateMatches(in string: String, options: MatchingOptions = [], range: Range<String.Index>, using block: MatchClosure) rethrows { // We wrap all of the previous logic inside of another closure // that will be executed by `withoutActuallyEscaping(_:do:)` // so that the error we `rethrow` will happen inside of another // executing closure - the one passed to our enumerate method. try withoutActuallyEscaping(block) { (_block) in var error: Error? = nil let nsRange = NSRange(range, in: string) enumerateMatches(in: string, options: options, range: nsRange) { (results: NSTextCheckingResult?, flags: MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) in var _stop: Bool = false // Trap the error and save it to a variable // outside the closure. do { try _block(results, flags, &_stop) } catch let e { // Once we've trapped and saved the error we set // the `_stop` predicate to `true` to exit out of // the enumeration. error = e _stop = true } stop.pointee = ObjCBool(_stop) } // Finally, if we did set an error inside the // closure then we can rethrow it here. if let e = error { throw e } } } }
I will post other uses of withoutActuallyEscaping(_:do:) as I come across them.