14

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.

ph1lb4
  • 1,982
  • 17
  • 24
  • `GenericSubclass.self` ? – Özgür Ersil Feb 13 '17 at 13:30
  • It's unclear what you are going to accomplish. It the actual (sub)type does not matter why do you use the `if - else` expression since the common type is `Composite` ? If you need to access properties add them to the protocol declaration. – vadian Feb 13 '17 at 13:32
  • 1
    If there is some shared API between `GenericSubclass`es, you should move it up to `Composite` and just use that – Alistra Feb 13 '17 at 13:33
  • Sorry for being unspecific. I updated the question. I need to pass the generic type in another class, however I do not need to know specifically which generic type it is. – ph1lb4 Feb 13 '17 at 13:42
  • Any suggestions for the updated question? – ph1lb4 Feb 22 '17 at 09:21
  • But if you don't know the generic placeholder `T`, what should the new instance of `SomeOtherClass` be statically typed as? You cannot talk in terms of a generic type without its placeholders. – Hamish Feb 22 '17 at 22:12
  • Related: a [protocol can be used](http://stackoverflow.com/questions/32645612/check-if-variable-is-an-optional-and-what-type-it-wraps/32781143#32781143) to be able to check `as?` agains a generic type. – Maic López Sáenz Feb 27 '17 at 18:30

4 Answers4

7

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
    }
}
Dave Weston
  • 6,527
  • 1
  • 29
  • 44
  • Great answer! As of Swift 3 this is one of the few alternatives to deal with the lack of [covariance nor contravariance](https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29) and that generic types must be defined during compile time. – Maic López Sáenz Feb 27 '17 at 18:22
0

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.

zombie
  • 5,069
  • 3
  • 25
  • 54
  • Depending on the string description to determine a class or generic type is not recommended because the strings can change without warning and because it is not tied to the class syntax. – Maic López Sáenz Feb 27 '17 at 18:11
0

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]
Wilson XJ
  • 1,750
  • 13
  • 14
  • I think `let array = [GenericSubclass(),GenericSubclass()]` doesn't compile because you're asking the compiler to infer the type of the array based on its contents, and I'm not sure how hard it will try to do that. When you tell the compiler it's an array of `Composite`, it just has to verify that each element you provide matches that. – Dave Weston Feb 24 '17 at 16:55
  • @DaveWeston Yes you are right but [Any] or [Composite] is not we want for the genericFunc,we need the exact type with ,so we need to separate them. – Wilson XJ Feb 24 '17 at 18:16
  • Yeah, I think we'll have to wait and see what works for the person who posted the question. – Dave Weston Feb 24 '17 at 19:43
0

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"]
Nikolay Shubenkov
  • 3,133
  • 1
  • 29
  • 31