在Swift中实现闭包协议



我想在我的Swift项目中创建一些函数,可以接受对象,也可以接受返回该对象类型的闭包。当然,我可以在每个地方用多个签名定义同一个函数,但那太啰嗦了。我也希望能够创建这些对象/对象返回闭包的类型安全列表,但如果没有描述这两件事的通用类型,就无法做到这一点。

这就是我想要做的

typealias StringClosure = () -> String
protocol Stringable {
    func toStringClosure() -> StringClosure
}
extension String : Stringable {
    func toStringClosure() -> StringClosure {
        return { return self }
    }
}
extension StringClosure : Stringable {
    func toStringClosure() -> StringClosure {
        return self
    }
}
func printStringable(a : Stringable) {
    print(a.toStringClosure()())
}

var stringableList : Stringable[] = ["cat", {return "dog"}, "gecko"]
for stringable in StringableList {
    printStringable(stringable)
}

但这不起作用,因为我实际上不能扩展我的StringClosure类型来实现Stringable。我可以使stringableList成为Any类型的列表,但这不是类型安全的。

枚举解

一个解决方案是,我可以创建一个枚举类型,但这意味着我必须在使用枚举的任何地方显式注释这些类型,这很蹩脚。它看起来像这样:

enum StringableEnum {
    case Str(String)
    case Fun(StringClosure)
}
func printStringableEnum(a : StringableEnum) {
    switch (a) {
    case let .Str(value):
        print(value)
    case let .Fun(value):
        print(value())
    }
}
var enumList : StringableEnum[] = [.Str("cat"), .Fun({return "dog"}), .Str("gecko")]
for element in enumList {
    printStringableEnum(element)
}

这还不错,但它要求我的API的用户现在知道这个enum,并在每次调用我的printStringableEnum函数时用.Str.Fun标记他们的参数。这不是一个很好的API!

这可能对语言要求太高了,但是有人有更好的想法吗?

我认为在这种情况下,最简单的解决方案可能是最好的。不允许直接传入string,而是要求使用闭包。将字符串转换为闭包并不难:

let myString = "Hello"
printStringable({return  myString})

为了方便,你甚至可以创建一个函数将值转换成闭包:

func f<T>(value : T) -> () -> T  {
    return {return value}
}
printStringable(f("Hello"))
printStringable(f(myString))

虽然我不认为保存的几个字符是值得的,但可能令人困惑的函数名。

编辑:

还可以这样改进枚举:

enum StringableEnum {
    case Str(String)
    case Fun(() -> String)
    init(_ string : String) {
        self = .Str(string)
    }
    init(_ closure : () -> String) {
        self = .Fun(closure)
    }
    var value : String {
        switch(self) {
            case let .Str(value):
                return value
            case let .Fun(closure):
                return closure()
        }
    }
}

这意味着您可以从任何支持的类型创建枚举,如下所示:

var stringable = StringableEnum("Hello")
stringable = StringableEnum({return "Hello"})

你可以通过

把字符串取出来
stringable.value

EDIT:不要做以下事情。它可以在Beta 4中使用,但依赖于一个苹果不希望公众使用的功能,并打算在Swift 1.0发布之前删除。


@auto_closure可能适合您,但问题是,似乎您不能将显式闭包传递给@auto_closure参数。

另一种选择是将所有内容都视为闭包,并使用Swift的__conversion特性在需要的地方隐式地将字符串转换为stringclosure:

typealias StringClosure = () -> String
extension String {
    func __conversion() -> StringClosure {
        return { self } // 'return' can be omitted inside a single-expression closure
    }
}
// type inferencing automatically figures out that
// stringableList should be [StringClosure] and applies
// the conversion method to promote the string entries into
// self-returning closures
var stringableList = ["cat", { println("side-effects are bad"); return "dog"}, "gecko"]
func printStringClosure(s: StringClosure) {
    println(s())
}
for s in stringableList {
    printStringClosure(s)
}
printStringClosure("test")
printStringClosure { let a = 5*5; return "(a)" }

不要忘记swift有创建惰性计算变量的选项,就像这样(它们不限于类/结构):

var lazy : String {
    return "Hello"
}
printStringable(lazy)

如果你不需要参数化的闭包,这是一个更简单的解决你的延迟计算/闭包有副作用的问题。

最新更新