theDuncs
theDuncs

Reputation: 4843

Swift closures - force a closure to always complete

Is it possible to force a closure to be completed? In the same way that a function with a return value MUST always return, it would be ace if there was a way to force a closure to contain the syntax necessary to always complete.

For example, this code will not compile because the function does not always return a value:

func isTheEarthFlat(withUserIQ userIQ: Int) -> Bool {
    if userIQ > 10 {
        return false
    }
}

In the exact same way, I would like to define a function with a closure, which will also not compile if the closure never returns. For example, the code below might never return a completionHandler:

func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) {
    if userIQ > 10 {
        completionHandler(false)
    }
}

The code above compiles, but I was wondering if there is a keyword which enforces that the closure sends a completion handler in all cases. Maybe it has something to do with the Void in the above function?

Upvotes: 4

Views: 681

Answers (3)

Alexander
Alexander

Reputation: 63331

Here is an interesting technique I thought of. You define GuarenteedExecution and GuarenteedExecutionResult types.

A GuarenteedExecution is a wrapper around a closure, which is to be used in a context where the execution of the closure must be guaranteed.

The GuarenteedExecutionResult is the result of executing a GuarenteedExecution. The trick is to have a desired function, e.g. isTheEarthFlat, return a GuarenteedExecutionResult. The only way to obtain a GuarenteedExecutionResult instance is by calling execute(argument:) on a GuarenteedExecution. Effectively, the type checker features responsible for guaranteeing a return, are now being used to guarantee the execution of GuarenteedExecution.

struct GuarenteedExecutionResult<R> {
    let result: R

    fileprivate init(result: R) { self.result = result }
}

struct GuarenteedExecution<A, R> {
    typealias Closure = (A) -> R

    let closure: Closure

    init(ofClosure closure: @escaping Closure) {
        self.closure = closure
    }

    func execute(argument: A) -> GuarenteedExecutionResult<R> {
        let result = closure(argument)
        return GuarenteedExecutionResult(result: result)
    }
}

Example usage, in a seperate file (so as to not have access to GuarenteedExecutionResult.init):

let guarenteedExecutionClosure = GuarenteedExecution(ofClosure: {
    print("This must be called!")
})

func doSomething(guarenteedCallback: GuarenteedExecution<(), ()>)
    -> GuarenteedExecutionResult<()> {
    print("Did something")
    return guarenteedCallback.execute(argument: ())
}

_ = doSomething(guarenteedCallback: guarenteedExecutionClosure)

Upvotes: 2

Alexey Kozhevnikov
Alexey Kozhevnikov

Reputation: 4259

There is no special keyword for what you want. But there is an interesting approach you can take into consideration, that won't compile:

func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) {
    let result: Bool
    defer {
       completionHandler(result)
    }
    if userIQ > 10 {
        result = false
    }
}

that will do and is completionHandler is forced to be called:

func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) {
    let result: Bool
    defer {
       completionHandler(result)
    }
    if userIQ > 10 {
        result = false
    } else {
        result = true
    }
}

Not sure it's a good pattern to use.

Upvotes: 2

rmaddy
rmaddy

Reputation: 318874

No, there is no language construct that will result in a compiler error if you forget (or don't need) to call the completion handler under all possible conditions like a return statement.

It's an interesting idea that might make a useful enhancement to the language. Maybe as a required keyword somewhere in the parameter declaration.

Upvotes: 2

Related Questions