rdv0011
(Dmitry)
1
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:
- How to refer to generics in the properties?
- 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()
Mordil
(Nathan Harris)
2
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
rdv0011
(Dmitry)
3
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()