0
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?

4
  • Is this from a completely arbitrary selection of classes and arguments or is it a well defined and limited selection? If it is the latter maybe a factory class could be a solution. Commented Feb 1, 2020 at 18:17
  • It is the latter. What do you mean specifically with a factory class? I could see how to do it in C++, but in swift, I am unclear how to do it. Commented Feb 1, 2020 at 18:24
  • Once you called A(val: 4), what would be the next thing you'd do with that, since you don't know anything about the returned value (such as what properties it has)? There's no way to do what you've literally said here, but there are quite a few ways to achieve what you're hinting at. The solution that makes sense depends on the goal, though. Commented Feb 1, 2020 at 21:50
  • All the classes I would do this on (including A) derive from a same base class, which provide the common functionalities I am interested in. Commented Feb 1, 2020 at 21:55

2 Answers 2

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 {}
Sign up to request clarification or add additional context in comments.

2 Comments

The problem is that some of the initializers have multiple parameters. And it's not even clear to me that they have the same types. See the linked question (I would describe this as a "distributed" question, the OP keeps doling out pieces of it).
Thanks Rob, the first part of your question solves my problem well. matt provides incorrect information and only focuses on the negative, so I appreciate someone actually focused on helping.
1

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).

1 Comment

Well, the way to get a class from a string is with NSClassFromString. And the way to make an arbitrary method call is with NSInvocation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.