简体   繁体   English

使用struct / protocols进行Swift多态闭包调度

[英]Swift polymorphic closure dispatch with struct/protocols

I have a case where I want to register either one argument or no argument closures with a service. 我有一种情况,我想向服务注册一个参数或没有参数闭包。 There's always an argument available, but for brevity, I want to be able to register no arg closures as well, and then just dispatch the closure without the available argument in that case. 总是有一个可用的参数,但是为了简洁起见,我也希望不注册任何arg闭包,然后在这种情况下仅在没有可用参数的情况下分派闭包。 Coming from a strong OO and dynamic types background where we love polymorphic dispatch and class inheritance trees and let the types figure themselves out, I can throw the following together: 来自强大的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)

So I see 42 followed by something happened in my console. 所以我看到42之后控制台中something happenedsomething happened Great. 大。

But I'd like to explore doing this with struct and/or enum . 但是我想探索使用struct和/或enum做到这一点。 Value semantics are all the rage. 价值语义风靡一时。 The enum approach was relatively straightforward, I think: 我认为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)

To do a struct approach, I it is my understanding that I should use a protocol to capture common interface of publish(value:T) . 做一个struct方法,据我了解,我应该使用一个协议来捕获publish(value:T)通用接口。 But that's where things get confusing, because protocols apparently can't be mixed with generics? 但这就是令人困惑的地方,因为协议显然不能与泛型混合使用吗? I tried: 我试过了:

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)

This just produces a lot of errors at the bottom. 这只会在底部产生很多错误。 I had tried to do the extensions as generics (eg extension NullaryAction<T>:Action ), but it told me that T was unused, even though I had placed the typealias expressions in the extensions. 我曾尝试将扩展名作为泛型来执行(例如, extension NullaryAction<T>:Action ),但即使我将typealias表达式放入扩展名中,它也告诉我T未使用。

Is it possible to do this with struct/protocol? 是否可以用struct / protocol做到这一点? I'm happy with the enum solution, but was disappointed I couldn't realize it with the struct/protocol approach. 我对枚举解决方案感到满意,但对使用struct / protocol方法无法实现它感到失望。

Judging by the fact that you want to cast your structs to their protocols (by using var action: Action = UnaryAction {...} ), I'm assuming you don't need the publish method to have the right signature upon evoking it. 从您想要将结构var action: Action = UnaryAction {...}为协议的事实(通过使用var action: Action = UnaryAction {...} )来var action: Action = UnaryAction {...} ,我假设您不需要publish方法来在调用它时具有正确的签名。

In other words, by declaring your Action protocol with a typealias , the compiler is expecting to specialise the publish method for each instance of your structs. 换句话说,通过使用typealias声明您的Action协议,编译器期望为结构的每个实例专门化publish方法。

This means that you have two options: 这意味着您有两个选择:

  1. Remove the type casting: 删除类型转换:

Example: 例:

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)

This solution makes your publish methods also have the same signature that your structs have. 此解决方案使您的publish方法也具有与结构相同的签名。 If your struct is specialised to work with Ints , then you'll have .publish(value: Int) . 如果您的结构专用于Ints ,则将具有.publish(value: Int)

  1. Make the protocol non-generic 使协议非通用

Example: 例:

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)

This solution allows you to keep on type casting, but your publish methods will all have the same signature ( .publish(value: Any) ). 此解决方案允许您继续进行类型转换,但是您的publish方法都将具有相同的签名( .publish(value: Any) )。 You also need to account for that upon executing the closure. 您还需要在执行关闭时考虑到这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM