【问题标题】:Swift polymorphic closure dispatch with struct/protocols带有结构/协议的 Swift 多态闭包调度
【发布时间】:2015-12-10 20:36:27
【问题描述】:

我有一个案例,我想向服务注册一个参数或不注册参数闭包。总是有一个可用的参数,但为简洁起见,我希望能够不注册任何 arg 闭包,然后在这种情况下只调度没有可用参数的闭包。来自强大的 OO 和动态类型背景,我们喜欢多态调度和类继承树并让类型自己解决,我可以将以下内容放在一起:

class AbstractAction<T> {
    func publish(value:T) {
        fatalError("you should override this")
    }
}

class NullaryAction<T>: AbstractAction<T> {
    var closure:() -> ()
    override func publish(_:T) {
        closure()
    }
    init(closure:()->()) {
        self.closure = closure
    }
}

class UnaryAction<T>: AbstractAction<T> {
    var closure:(T) -> ()
    override func publish(value:T) {
        closure(value)
    }
    init(closure:(T)->()) {
        self.closure = closure
    }
}

var action:AbstractAction = UnaryAction<Int>(closure: { print("\($0)") })
action.publish(42)
action = NullaryAction<Int>(closure: { print("something happened") } )
action.publish(42)

所以我在控制台中看到42,后跟something happened。太好了。

但我想通过struct 和/或enum 来探索这样做。价值语义风靡一时。我认为enum 方法相对简单:

enum Action<T> {
    case Nullary( ()->() )
    case Unary( (T)->() )

    func publish(value:T) {
        switch self {
        case .Nullary(let closure):
            closure()
        case .Unary(let closure):
            closure(value)
        }
    }
}

var action = Action.Unary({ (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = Action<Int>.Unary( { print("shorthand too \($0)") } )
action.publish(42)
action = Action<Int>.Nullary({ print("something happened") })
action.publish(42)

要做struct的方法,我的理解是我应该使用协议来捕获publish(value:T)的公共接口。但这就是事情变得混乱的地方,因为协议显然不能与泛型混合?我试过了:

struct NullaryAction<T> {
    typealias ValueType = T
    var closure:() -> ()
}

struct UnaryAction<T> {
    typealias ValueType = T
    var closure:(T) -> ()
}

protocol Action {
    typealias ValueType
    func publish(value:ValueType)
}

extension NullaryAction: Action {
    func publish(_:ValueType) {
        self.closure()
    }
}

extension UnaryAction: Action {
    func publish(value:ValueType) {
        self.closure(value)
    }
}

var action:Action = UnaryAction(closure: { (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too \($0)") } )
action.publish(42)
action = NullaryAction<Int>(closure:{ print("something happened") })
action.publish(42)

这只会在底部产生很多错误。我曾尝试将扩展作为泛型(例如extension NullaryAction&lt;T&gt;:Action),但它告诉我T 未被使用,即使我已将typealias 表达式放在扩展中。

是否可以使用结构/协议来做到这一点?我对枚举解决方案感到满意,但对我无法通过结构/协议方法实现这一点感到失望。

【问题讨论】:

  • 协议和泛型在 Swift 中可能相当棘手。也许此线程中的答案可以帮助您:milen.me/writings/swift-generic-protocols(另请参阅 forums.developer.apple.com/thread/7350)。然而,这两者都与具有某些协议函数的通用返回类型有关,但也许您可以使用动态类型魔法来解决问题:) 对于您的具体示例,我很想知道结果(尝试修复某些问题但失败了相当悲惨...)

标签: swift swift2 swift-protocols swift-structs


【解决方案1】:

根据您想将结构转换为它们的协议(通过使用var action: Action = UnaryAction {...})这一事实判断,我假设您不需要publish 方法在调用它时具有正确的签名。

换句话说,通过使用typealias 声明您的Action 协议,编译器期望为您的结构的每个实例专门化publish 方法。

这意味着您有两种选择:

  1. 移除类型转换:

例子:

var action /*removed the :Action type casting */ = UnaryAction<Int>(closure: { (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too \($0)") } )
action.publish(42)
var anotherAction = NullaryAction<Int>(closure:{ print("something happened") }) //need to use another variable for this last one
anotherAction.publish(42)

此解决方案使您的 publish 方法也具有与您的结构相同的签名。如果您的结构专门用于Ints,那么您将拥有.publish(value: Int)

  1. 使协议成为非泛型

例子:

protocol Action {
    func publish(value:Any)
}

struct NullaryAction<T>: Action {
    let closure: () -> ()
    init(closure: () -> ()) {
        self.closure = closure
    }
    func publish(value:Any) {
        self.closure()
    }
}

struct UnaryAction<T>: Action {
    let closure: (T) -> ()
    init(closure: (T) -> ()) {
        self.closure = closure
    }
    func publish(value:Any) {
        self.closure(value as! T) //Need to type cast here
    }
}

var action: Action = UnaryAction<Int>(closure: { (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too \($0)") } )
action.publish(42)
action = NullaryAction<Int>(closure:{ print("something happened") })
action.publish(42)

此解决方案允许您继续进行类型转换,但您的 publish 方法都将具有相同的签名 (.publish(value: Any))。您还需要在执行关闭时考虑到这一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多