【问题标题】:How can you define a variable that can hold one of a specific set of closure signatures?你如何定义一个可以保存一组特定闭包签名的变量?
【发布时间】:2018-08-26 05:31:34
【问题描述】:

考虑下面的代码。它允许您通过传入可以处理有效负载的闭包来实例化处理器。闭包可以是六种签名类型之一。以下是我的实现方式:

我想知道是否有更简单/更好的方法来写这个。我可能正在考虑使用具有关联值的枚举,但这意味着我必须将闭包包装在枚举中,然后将枚举传递给初始化程序。我希望尽可能避免这种情况。

struct Payload {
    let stringVal : String
    let intVal    : Int
}

class Processor{

    init(action:Any){

        switch action {
            case let action as ()       -> Void,
                 let action as ()       -> Error?,
                 let action as (String) -> Void,
                 let action as (String) -> Error?,
                 let action as (Int)    -> Void,
            case let action as (Int)    -> Error?,
            default: fatalError("Action does not have a supported signature")
        }

        self.action = action
    }

    let action:Any

    func performAction(payload:Payload) -> Error? {

        switch action {

            case let action as () -> Void:
                action()

            case let action as () -> Error?:
                return action()

            case let action as (String) -> Void:
                action(payload.stringVal)

            case let action as (String) -> Error?:
                return action(payload.stringVal)

            case let action as (Int) -> Void:
                action(payload.intVal)

            case let action as (Int) -> Error?:
                return action(payload.intVal)

            default: fatalError("Action does not have a supported signature")
        }

        return nil
    }
}

另外,在我执行模式匹配以确定我是否有有效匹配的初始化程序中,我收到一个编译器警告,说我已经设置,但从未使用过action。但是,如果我将其替换为 _,则会收到一条警告,指出我没有绑定到模式中的任何内容。如何在对我不关心值是什么的模式执行测试的同时抑制警告?

【问题讨论】:

  • 我认为具有关联类型的枚举将是正确的方法。
  • 是的,这也是我不断返回的内容。但是如果我走那条路,有没有办法仍然将Any 传递给初始化程序并映射到里面的正确枚举?我问是因为枚举在函数之外在技术上没有任何意义,唯一的例外是给初始化器一个包装器。

标签: swift pattern-matching swift4


【解决方案1】:

避免使用Any 类型的参数。使用具有关联类型的枚举:

enum Action {
    case a1(() -> Void)
    case a2(() -> Error?)
    case a3((String) -> Void)
    case a4((String) -> Error?)
    case a5((Int) -> Void)
    case a6((Int) -> Error?)
}

struct Payload {
    let stringVal : String
    let intVal    : Int
}

class Processor{
    let action: Action

    init(action: Action) {
        self.action = action
    }

    func performAction(payload: Payload) -> Error? {
        var error: Error? = nil
        switch action {
        case let .a1(closure):
            closure()
        case let .a2(closure):
            error = closure()
        case let .a3(closure):
            closure(payload.stringVal)
        case let .a4(closure):
            error = closure(payload.stringVal)
        case let .a5(closure):
            closure(payload.intVal)
        case let .a6(closure):
            error = closure(payload.intVal)
        }

        return error
    }
}

这确保您只能将有效操作传递给您的 Payload 类。

Action case 值需要一个更好的命名约定,但这会让你开始。

这是一个用法示例:

let payload = Payload(stringVal: "Hello", intVal: 42)
let myAction = {
    print("Working")
}
let action = Action.a1(myAction)
let processor = Processor(action: action)
let error = processor.performAction(payload: payload)

另一种方法是将Action 设置为Processor 内部,并为Processor 提供6 个初始化器。

struct Payload {
    let stringVal : String
    let intVal    : Int
}

class Processor{
    enum Action {
        case a1(() -> Void)
        case a2(() -> Error?)
        case a3((String) -> Void)
        case a4((String) -> Error?)
        case a5((Int) -> Void)
        case a6((Int) -> Error?)
    }

    private let action: Action

    init(_ closure: @escaping () -> Void) {
        self.action = .a1(closure)
    }

    init(_ closure: @escaping () -> Error?) {
        self.action = .a2(closure)
    }

    init(_ closure: @escaping (String) -> Void) {
        self.action = .a3(closure)
    }

    init(_ closure: @escaping (String) -> Error?) {
        self.action = .a4(closure)
    }

    init(_ closure: @escaping (Int) -> Void) {
        self.action = .a5(closure)
    }

    init(_ closure: @escaping (Int) -> Error?) {
        self.action = .a6(closure)
    }

    func performAction(payload: Payload) -> Error? {
        var error: Error? = nil
        switch action {
        case let .a1(closure):
            closure()
        case let .a2(closure):
            error = closure()
        case let .a3(closure):
            closure(payload.stringVal)
        case let .a4(closure):
            error = closure(payload.stringVal)
        case let .a5(closure):
            closure(payload.intVal)
        case let .a6(closure):
            error = closure(payload.intVal)
        }

        return error
    }
}

let payload = Payload(stringVal: "Hello", intVal: 42)
let myAction = {
    print("Working")
}
let processor = Processor(myAction)
let error = processor.performAction(payload: payload)

【讨论】:

  • 是的,这就是我一直倾向于的方式,因为与我的解决方案不同,这可以在编译时进行验证。但是,我认为不是使用枚举包装函数的初始化程序,而是创建六个初始化程序,每个签名一个,然后在内部使用枚举。这样外界甚至不需要知道它。如果您更新对该方法的答案,我会将其标记为已接受,而不是自己回答(不是您需要积分!哈哈)
  • 我只是用一个例子来更新答案。我想Action 枚举可以移动到Processor 类中,Processor 可以更新为有 6 个初始化程序以隐藏调用者对Action 的任何使用。
  • 这正是我刚才在评论中所说的。我认为这是一个更干净的界面,不是吗?那么外面的人就不需要知道这个枚举了。它成为“黑匣子”的一部分。再说一次,如果你走那条路,为什么不只有六个可选成员变量,每个代表一个类型?然后你可以 if-let 解开它们,如果你得到它,调用它。这也会给你类型安全,就像枚举一样,但不需要枚举。想法?
  • 对,我只是同意你的观点。
  • 对六个私有可选成员变量而不是枚举的想法?
猜你喜欢
  • 2016-10-22
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多