Dynamically instantiate generics

I need to instantiate a class with generics. What I came up with is a straightforward a bit ugly solution only. I wonder whether more elegant solution exists or not. What I want to achieve in a real app is to instantiate different implementations that have generics parameters based on the settings in the UserDefaults.

There are two problems:

  1. How to refer to generics in the properties?
  2. How to instantiate generics with dynamic type binding?
import UIKit

protocol Joint {
    associatedtype T
    var count: T { get }
}

class SingleJoint: Joint {

    var count: Float {
        1
    }
}

class MultipleJoints: Joint {

    var count: Int {
        10
    }
}

// Ugly solution
class ViewModel1 {
    var singleJoint: SingleJoint? = nil
    var multipleJoints: MultipleJoints? = nil

    private var decisionMakeCondition: Bool {
        true
    }

    var joints: String {
        if self.decisionMakeCondition {
            return String(singleJoint?.count ?? 0)
        } else {
            return String(multipleJoints?.count ?? 0)
        }
    }

    init() {
        if self.decisionMakeCondition {
            singleJoint = SingleJoint()
        } else {
            multipleJoints = MultipleJoints()
        }
    }
}

// Might be a more elegant solution but it does not compile (
class ViewModel2<J: Joint> {

    var joints: String {
        "\(joint.count)"
    }

    private let joint: J!

    private var decisionMakeCondition: Bool {
        true
    }

    init() {
        // Problem 2. How to instantiate a type with generics?
        // Different implementations might be instantiated based on a condition
        if self.decisionMakeCondition {
            joint = SingleJoint()
        } else {
            joint = MultipleJoints()
        }
    }
}

// I want to avoid introducing generic types here i.e. ```ViewController<T: Joint>: UIViewController```

class ViewController: UIViewController {
    // Problem 1. How to refer to the type with generics?
    let model = ViewModel2()
    //let model = ViewModel1()

    func test() {
        print("Joints \(model.joints)")
    }
}

let controller = ViewController()
controller.test()

This is a case for type erasure, which usually can be done pretty easily with protocols.

You would define a new protocol called ViewModelProtocol (to avoid clashing with your existing ViewModel names in the example):

protocol ViewModelProtocol {
    var joints: String { get }
    init()
}

and then conform ViewModel2 to it with just:

extension ViewModel2: ViewModelProtocol { }

and then in your view controller:

class ViewController: UIViewController {
    let model: ViewModelProtocol

    func test() {
        print("Joints \(model.joints)")
    }
}

This is what's known as an "existential" protocol, and is currently only possible with protocols that do not have Self or associatedtype requirements, like your Joint protocol does.

1 Like

A workable solution inspired by @Mordil with my comments

import UIKit

// Type erasure example

protocol Joint {
    associatedtype T
    var count: T { get }
}

class SingleJoint: Joint {

    var count: Float {
        1
    }
}

class MultipleJoints: Joint {

    var count: Int {
        10
    }
}

protocol ViewModelProtocol {
    var joints: String { get }
}

class ViewModel2<T: Joint> {

    private let joint: T

    init(joint: T) {
        self.joint = joint
    }
}

// Here we do type erasing
// To be able to assign ViewModel2 instance to a property that confirms to ViewModelProtocol later
extension ViewModel2: ViewModelProtocol {
    var joints: String {
        "\(joint.count)"
    }
}

// Introducing generic types to the view controller is not an option
class ViewController: UIViewController {
    let model: ViewModelProtocol = ViewController.modelInstance()

    func test() {
        // Here we refer to a non generic protocol
        // that delegates a call to a corresponding generic implementation i.e. SingleJoint or MultipleJoints
        print("Joints \(model.joints)")
    }
}

extension ViewController {

    private static var decisionMakeCondition: Bool {
        true
    }

    // This is where the magic happens
    // We can return an instance of a generic type assigning it to non generic one
    private static func modelInstance() -> ViewModelProtocol {
        if self.decisionMakeCondition {
            return ViewModel2(joint: SingleJoint())
        } else {
            return ViewModel2(joint: MultipleJoints())
        }
    }
}

let controller = ViewController()
controller.test()