cappie013
cappie013

Reputation: 2444

Generic function in Swift

I am from C++ and I'm a little confused about generic function in Swift.

In C++, I can do that :

class Entity
{
    template    <typename T>
    T *getComponent()
    {
        return (T *)(listComponent_[typeid(T).name()]);
    }
};

and I can do this to get a specific Component from my Entity

Entity().getComponent<LifeComponent>();

I would like to reproduce this in Swift, but I can't figure out how to achieved it.

In Swift, I have to defined a parameter to my function to determine my type.

In Swift :

class Entity
{
    func get<T>() -> T
    {
         return ??
    }
}

I would like to be able to do :

Entity().get<LifeComponent>()

Can you help me ?

Upvotes: 0

Views: 929

Answers (4)

Stefan Jager
Stefan Jager

Reputation: 141

Just stumbled upon the same question. How to call a generic function?

Found a solution for me, maybe it will work for you too. I have some structs that I want to initialize from dictionary. All my structs implement a protocol DictionaryInitializable.

struct MyStruct: DictionaryInitializable {
    ...
    init?(dictionary: [String: AnyObject]) {
        ...
    }
}

Also I have a parser with the following method:

func parse<T: DictionaryInitializable>(dict: [String: AnyObject]) -> T? {
    return T(dictionary: dict)
}

To use that function, you can use code like the following:

let parsedStruct: MyStruct = parser.parse(dictionary)

This works in Xcode 7.3 with Swift 2.2. Never tested in versions before that.

Upvotes: 0

rintaro
rintaro

Reputation: 51911

I don't know C++ syntax, but maybe what you want is like this?

class Entity {
    var list:[Any] = []

    func register(obj:Any) {
        list.append(obj)
    }
    func getComponent<T>(type:T.Type) -> T? {
        for obj in list {
            if let instance = obj as? T {
                return instance
            }
        }
        return nil
    }
}

let cls = Entity()
cls.register(1)
cls.register("This is a String")
cls.register(UIView())
cls.register(1.01)

cls.getComponent(String) // -> Optional("This is a String")

Upvotes: 3

David Berry
David Berry

Reputation: 41226

This is as close as I could come to the spirit of what you were trying to do.

In this case we're defining a protocol that tells the Entity how to get the name of the component. Making it explicit will make it much more reliable and legible than any of the hacks that abound to get the type name of a variable or the name of a class, all of which have multiple limitations. Note that this can be significantly simplified if components all have a common super class, or even if they're all subclasses of NSObject Using the protocol approach is probably best anyway, since the things in your Entity, whatever they are, should all have some common shared interface, otherwise you don't know what you can do with them anyway.

// Mostly the Component protocol is needed so that we can create an
//  item with no arguments.
protocol NamedComponent {
    class var ComponentName : String { get }
}

// Make Int an allowable component
extension Int : NamedComponent {
    static let ComponentName = "Int"
}

extension String : NamedComponent {
    static let ComponentName = "String"
}

// make a class a NamedComponent, note the different syntax between
// adding a class constant to a class here and to a struct or base type
// above
class SomeComponent : NamedComponent {
    class var ComponentName : String { get { return "SomeComponent" } }
}

class Entity {
    // The component library
    var components = [String:NamedComponent]()

    // add a component to the library by class name
    func register<T:NamedComponent>(value:T) {
        components[T.ComponentName] = value
    }

    // Reference a component by class name
    func get<T:NamedComponent>() -> T? {
        return components[T.ComponentName] as? T
    }
}

// Create the entity
let entity = Entity()

// Add a dummy component
entity.register(45)
entity.register("string")
entity.register(SomeComponent())

// Fetch a dummy component
let int : Int? = entity.get()
println("\(int)")

let str : String? = entity.get()
println("\(str)")

let comp : SomeComponent? = entity.get()
println("\(comp)")

Upvotes: 0

Abhi Beckert
Abhi Beckert

Reputation: 33359

You need to tell the compiler what it can do with T, or else it's pretty much useless.

Swift must know, at compile time what you'll pass into the get() function. It will not blindly accept whatever chunk of memory you provide. The compiler has to be told what will be in that memory.

Here's the documentation describing how to use generics in Swift, which should answer all your questions: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html

Another option is you can just use Objective-C or even C++.

Most software for iOS and OS X is written in Objective-C or C/C++. Hardly anything is currently written in Swift and the language is years from being a fully viable language. A project written mostly in Swift can integrate nicely with code written in Objective-C or C/C++.

Upvotes: 0

Related Questions