How can I generate an init call from strings in swift

好久不见. 提交于 2020-02-06 08:03:44

问题


class A {
  let val : Int
  init(val: Int) {
     self.val = val
  }
}

I have these 3 strings:

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

How can I call A(val:4) from using these 3 strings?


回答1:


Since you've noted in the comments that the types will all be subclasses of some supertype, then that supertype can handle all the dispatching. In Cocoa, this is a pretty common pattern known as a class cluster.

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
        }
    }
}

Now, I don't particularly like this approach. This kind of subclassing tends to be poor Swift. Swift is fine with classes when you require a reference type, but it doesn't favor subclassing. I'd do this with a Buildable protocol and a 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")

If this leads to duplicated code, there are straightforward ways to address that with other protocols. For example, if many of your types had init(val: Int), they could share code with another protocol:

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 {}



回答2:


In native Swift alone, you can't. Swift is not dynamic in such a way that you can instantiate an arbitrary class based on a string name of the class, and so forth. Objective-C is dynamic and has ways to do this, so if this kind of thing is important to you, make A an NSObject subclass and write this part of the code in Objective-C (or use equivalent Cocoa/objc-runtime calls).



来源:https://stackoverflow.com/questions/60019259/how-can-i-generate-an-init-call-from-strings-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!