【问题标题】:How can I generate an init call from strings in swift如何从 swift 中的字符串生成初始化调用
【发布时间】:2020-02-01 17:15:43
【问题描述】:
class A {
  let val : Int
  init(val: Int) {
     self.val = val
  }
}

我有这 3 个字符串:

let className = "A"
let argName = "val"
let argValue = "4"

如何使用这 3 个字符串调用 A(val:4)

【问题讨论】:

  • 这是完全任意选择的类和参数,还是定义明确且有限的选择?如果是后者,那么工厂类可能是一个解决方案。
  • 是后者。您对工厂课程的具体含义是什么?我可以看到如何在 C++ 中做到这一点,但在 swift 中,我不清楚如何做到这一点。
  • 一旦你调用了A(val: 4),接下来你会怎么做,因为你对返回值一无所知(比如它有什么属性)?没有办法做到你在这里所说的,但是有很多方法可以实现你所暗示的。不过,有意义的解决方案取决于目标。
  • 我会在上面执行此操作的所有类(包括 A)都派生自同一个基类,它们提供了我感兴趣的通用功能。

标签: swift initialization swift5


【解决方案1】:

由于您已经在 cmets 中注意到类型都将是某个超类型的子类,因此该超类型可以处理所有调度。在 Cocoa 中,这是一种非常常见的模式,称为类集群。

class SuperA {
    enum SuperAError: Error {
        case cannotConstruct
    }

    static func create(className: String, argName: String, argValue: String) throws -> SuperA {
        switch className {
        case "A":
            guard argName == "val",
                let value = Int(argValue)
                else { throw SuperAError.cannotConstruct }
            return A(val: value)

        default:
            throw SuperAError.cannotConstruct
        }
    }
}

现在,我不是特别喜欢这种方法。这种子类化往往是糟糕的 Swift。当您需要引用类型时,Swift 对类很好,但它不支持子类化。我会使用 Buildable 协议和 Builder 来做到这一点:

enum BuildableError: Error {
    case unknownType
    case badParameters
}

protocol Buildable {
    init(argName: String, argValue: String) throws
    // ... and the rest of the methods you require ...
}

struct A {
    var val: Int
}

extension A: Buildable {
    init(argName: String, argValue: String) throws {
        guard argName == "val", let value = Int(argValue) else {
            throw BuildableError.badParameters
        }

        self.init(val: value)
    }
}

final class Builder {
    var buildables: [String: Buildable.Type] = [:]

    func build(className: String, argName: String, argValue: String) throws -> Buildable {
        guard let buildable = buildables[className] else {
            throw BuildableError.unknownType
        }

        return try buildable.init(argName: argName, argValue: argValue)
    }
}

let builder = Builder()
builder.buildables["A"] = A.self
builder.build(className: "A", argName: "val", argValue: "4")

如果这会导致代码重复,可以通过其他协议直接解决此问题。例如,如果您的许多类型都有init(val: Int),它们可以与另一个协议共享代码:

protocol ValIntBuildable: Buildable {
    init(val: Int)
}

extension ValIntBuildable {
    init(argName: String, argValue: String) throws {
        guard argName == "val", let value = Int(argValue) else {
            throw BuildableError.badParameters
        }

        self.init(val: value)
    }
}

extension A: ValIntBuildable {}

【讨论】:

  • 问题是有些初始化器有多个参数。我什至不清楚它们是否具有相同的类型。请参阅链接的问题(我将其描述为“分布式”问题,OP 一直在分发其中的一部分)。
  • 谢谢 Rob,你问题的第一部分很好地解决了我的问题。马特提供了不正确的信息,只关注负面信息,所以我很欣赏有人真正专注于帮助。
【解决方案2】:

仅在原生 Swift 中,你不能。 Swift 不是动态的,您可以根据类的字符串名称实例化任意类,依此类推。 Objective-C 是动态的并且有办法做到这一点,所以如果这种事情对你很重要,请将 A 设为 NSObject 子类并在 Objective-C 中编写这部分代码(或使用等效的 Cocoa/objc-runtime 调用) .

【讨论】:

  • 嗯,从字符串中获取类的方法是使用 NSClassFromString。进行任意方法调用的方法是使用 NSInvocation。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多