ph1lb4
ph1lb4

Reputation: 2122

Cast to right generic from array in Swift

I have a Protocol called Composite.

This protocol has an array composites: [Composite]

I also have a generic subclass GenericSubclass<T>: Composite

When iterating over the array the best I can come up with looks like this:

for item in composites {
    if let item = item as? GenericSubclass<A> {
        let sc = SomeOtherClass<A>
    } else if let item = item as? GenericSubclass<B> {
        let sc = SomeOtherClass<B>
    } //and so on...
}

Is there any way to get a hold of GenericSubclass without specifying the Generic? In my use case there is absolutely no need for me to know about the T. I just have to instantiate another class with the same generic type.

Any help is much appreciated.

Upvotes: 14

Views: 1403

Answers (4)

Nikolay Shubenkov
Nikolay Shubenkov

Reputation: 3223

You need to add one method to protocol which creates new item of Type supported this protocol. So now you can use enums, structs and classes without any knowledge of creating object of specific type.
You can play in playground with the following code:

import UIKit

//This is your protocol
protocol MyAwesomeProtocol {
//this methods leaves implementaion detailes
//to concrete type 
    func createNewObject()->MyAwesomeProtocol
}

//Just create empty string
extension String: MyAwesomeProtocol {
    func createNewObject() -> MyAwesomeProtocol {
        return String()
    }
}

//create Enum with default value
extension UIControlState: MyAwesomeProtocol {
    func createNewObject() -> MyAwesomeProtocol {
        return UIControlState.normal
    }
}

//create viewController of any type
extension UIViewController: MyAwesomeProtocol {
    func createNewObject() -> MyAwesomeProtocol {
        return type(of:self).init()
    }
}

//This is test function
//it creates array of newly created items and prints them out 
//in terminal
func doSomeCoolStuffWith(items:[MyAwesomeProtocol]){

    var newItems = [MyAwesomeProtocol]()

    for anItem in items {
        let newOne = anItem.createNewObject()
        newItems.append(newOne)
    }

    print("created new ones:\n\(newItems)\nfrom old ones:\n\(items)\n")
}

doSomeCoolStuffWith(items: [UIControlState.focused,UIControlState.disabled])

doSomeCoolStuffWith(items: [UISplitViewController(),UINavigationController(),UICollectionViewController()])

doSomeCoolStuffWith(items: ["I","love","swift"])

This will produce the following result:

created new ones:
[__C.UIControlState(rawValue: 0), __C.UIControlState(rawValue: 0)]
from old ones:
[__C.UIControlState(rawValue: 8), __C.UIControlState(rawValue: 2)]

created new ones:
[<UISplitViewController: 0x7fa8ee7092d0>, <UINavigationController: 0x7fa8f0044a00>, <UICollectionViewController: 0x7fa8ee705f30>]
from old ones:
[<UISplitViewController: 0x7fa8ee7011e0>, <UINavigationController: 0x7fa8f004e600>, <UICollectionViewController: 0x7fa8ee708fb0>]

created new ones:
["", "", ""]
from old ones:
["I", "love", "swift"]

Upvotes: 0

Wilson XJ
Wilson XJ

Reputation: 1760

I think it's not possible to do that in array.

While you creat some different GenericSubclass<T> then put it in array , you will lose <T> no matter the composites is [Composite] or [Any].

// this line won't compile
let array = [GenericSubclass<Int>(),GenericSubclass<Double>()]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional

You want donging something like this func below, the param should be GenericSubclass<T> to compile success

func genericFunc<T>(param:GenericSubclass<T>) {
    let sc = SomeOtherClass<T>()
    print(sc)
}

Anyway you can implement it with member var for the instance like the code below:

class Subclass {
    var type : Any
    init(type : Any) {
        self.type = type
    }
}

class SomeOtherClass : CustomDebugStringConvertible{

    var type : Any
    init(type : Any) {
        self.type = type
    }
    var debugDescription: String{
        return String(describing: type.self)
    }
}

let array : [Subclass] = [Subclass(type : Int.self),Subclass(type : Double.self),Subclass(type : String.self)]

let scArray = array.flatMap {SomeOtherClass(type:$0.type.self)}
print(scArray) // prints [Int, Double, String]

Upvotes: 0

Dave Weston
Dave Weston

Reputation: 6635

It's not clear what you're trying to accomplish with the "generic" (pun intended) class names you've chosen. I don't think there's a way to directly accomplish what you want. I.e. you can't just leave it as a generic T because the compiler needs some way to determine what T will be in use at runtime.

However, one way to solve the issue is to hoist the API into the Composite protocol:

protocol Composite {
    var composites: [Composite] { get set }
    func otherClass() -> OtherProtocol
}

protocol OtherProtocol { }

class GenericSubclass<T>: Composite {
    var composites: [Composite] = []

    func otherClass() -> OtherProtocol {
        return SomeOtherClass<T>()
    }
}

class SomeOtherClass<T>: OtherProtocol {}

So now when you implement your loop, you can rely on the fact that since each element is a Composite, you know it must provide an instance of OtherProtocol via the otherClass() method:

var c = GenericSubclass<Int>()
c.composites = [GenericSubclass<Double>(), GenericSubclass<Int>(), GenericSubclass<Character>()]

for item in c.composites {
    let sc = item.otherClass()
    print(sc)
}

Alternatively, if only GenericSubclass should vend an OtherProtocol, you can make the return type Optional and define an extension for all the other implementations of Composite:

protocol Composite {
    var composites: [Composite] { get set }
    func optionalClass() -> OtherProtocol?
}

extension Composite {
    func optionalClass() -> OtherProtocol? {
        return nil
    }
}

Upvotes: 7

zombie
zombie

Reputation: 5259

I did some experiment on this in the playground and i came up with this

protocol Composite {
   var composites: [Composite] { get set }
}

class GenericSubclass<T>: Composite {
   var composites: [Composite] = []
}

let subclass = GenericSubclass<String>()

for item in subclass.composites {
   let className = String(describing: type(of: item))
   let aClassType = NSClassFromString(className) as! NSObject.Type
   let instance = aClassType.init() // we create a new object

   print(instance) //Output: GenericSubclass<String>
}

Hope this will help someone.

Upvotes: 0

Related Questions