Swift闭包 - 强制让闭包始终完成

4

有没有可能强制完成一个闭包?就像一个带有返回值的函数必须始终返回一样,如果有一种方法可以强制闭包包含必要的语法来始终完成,那将是很好的。

例如,这段代码无法编译,因为该函数并不总是返回一个值:

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

同样地,我想使用闭包定义一个函数,并且如果该闭包永远不返回,也不会编译。例如,下面的代码可能永远不会返回completionHandler:
func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) {
    if userIQ > 10 {
        completionHandler(false)
    }
}

上面的代码已经编译通过,但我想知道是否有一个关键字可以强制要求在所有情况下闭包都发送一个完成处理程序。也许与上面函数中的Void有关?


1
不是语言水平的问题,虽然我有一个有趣的想法,可以尝试一些东西来实现它。 - Alexander
你不能在方法的最后调用完成处理程序吗? - Sweeper
@Sweeper 我们大多数完成处理程序用于服务器访问,因此它们将返回(1)发生的任何错误和(2)来自服务器的数据。由于服务器错误不是我们想向用户显示的相同错误,因此我们经常有许多功能来决定错误是什么,同样,我们可能希望以特定方式操纵从服务器返回的数据。我所要求的是编译器确保在代码的所有分支中调用完成处理程序,就像在普通函数中必须返回返回值一样。 - theDuncs
我所要求的是编译器确保在代码的所有分支中调用完成处理程序 - 正如普通函数必须返回返回值的方式完全相同。你不能让编译器帮助你,但从程序员的角度来看,“defer”语句可以实现你想要的效果。 - matt
SE-0073本来可以实现这个功能,但由于命名原因(它被提议作为@noescape的扩展,而现在已经被弃用),以及底层实现需要大量工作,所以被拒绝了。但希望这是一个将来版本语言中会出现的功能。 - Hamish
3个回答

2

没有语言结构会在所有可能的情况下像return语句一样导致编译器错误,如果您忘记(或不需要)调用完成处理程序。

这是一个有趣的想法,可能会对语言进行有用的增强。也许可以将其作为参数声明中的required关键字。


2

你想要的并没有特定的关键词。但是有一种有趣的方法可以考虑,但不会编译:

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

这样做并且强制调用completionHandler

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

不确定这是一个好的使用模式。


2

这里有一个我想到的有趣技巧。你需要定义GuarenteedExecutionGuarenteedExecutionResult类型。

GuarenteedExecution是一个闭包的包装器,它应该在保证执行闭包的上下文中使用。

GuarenteedExecutionResult是执行GuarenteedExecution的结果。技巧在于让一个期望的函数,例如isTheEarthFlat返回一个GuarenteedExecutionResult。获得GuarenteedExecutionResult实例的唯一方法是在GuarenteedExecution上调用execute(argument:)。有效地,负责保证返回值的类型检查器特性现在被用来保证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)
    }
}

示例用法,在一个单独的文件中(以便无法访问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)

有趣的想法,唯一的问题是你必须为每个你想要的参数数量定义一个不同的GuaranteedExecution。例如,如果闭包的类型是(A, B) -> R(A, B, C) -> R等,那该怎么办呢? - Paolo
@Paolo 是的,但你可以使用元组。((X, Y, Z)) -> R) 匹配 (A) -> R,其中 A 是 (X, Y, Z) - Alexander

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接