125

I am trying to dynamically create a class instance based type using generics, however I am encountering difficulty with class introspection.

Here are the questions:

  • Is there a Swift-equivalent to Obj-C's self.class?
  • Is there a way to instantiate a class using the AnyClass result from NSClassFromString?
  • Is there a way to get AnyClass or otherwise type information strictly from a generic parameter T? (Similar to C#'s typeof(T) syntax)
Erik
  • 12,730
  • 5
  • 36
  • 42

7 Answers7

109

Well, for one, the Swift equivalent of [NSString class] is .self (see Metatype docs, though they're pretty thin).

In fact, NSString.class doesn't even work! You have to use NSString.self.

let s = NSString.self
var str = s()
str = "asdf"

Similarly, with a swift class I tried...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm… the error says:

Playground execution failed: error: :16:1: error: constructing an object of class type 'X' with a metatype value requires an '@required' initializer

 Y().me()
 ^
 <REPL>:3:7: note: selected implicit initializer with type '()'
 class X {
       ^

It took me a while to figure out what this means… turns out it wants the class to have a @required init()

class X {
    func me() {
        println("asdf")
    }

    required init () {

    }
}

let Y = X.self

// prints "asdf"
Y().me()

Some of the docs refer to this as .Type, but MyClass.Type gives me an error in the playground.

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
Jiaaro
  • 74,485
  • 42
  • 169
  • 190
  • 1
    Thank you for your link to the Metatype docs! I totally overlooked that aspect of Types, doh! – Erik Jun 05 '14 at 06:10
  • 14
    You can use `.Type` or `.Protocol` in variable declaration, e.g. `let myObject: MyObject.Type = MyObject.self` – Sulthan Jun 06 '14 at 07:54
  • 1
    Sulthan: so MyObject.Type is a declaration but MyObject.self is a factory method (can be called) and myObject is a variable containing a reference to a factory method. The call myObject() would produce an instance of class MyObject. It would be better example if the name of myObject variable was myObjectFactory? – bootchk Apr 24 '15 at 10:42
  • 2
    `@` before `required` should be deleted – fujianjin6471 Aug 22 '15 at 06:56
49

Here's how to use NSClassFromString. You have to know the superclass of what you're going to end up with. Here are a superclass-subclass pair that know how to describe themselves for println:

@objc(Zilk) class Zilk : NSObject {
    override var description : String {return "I am a Zilk"}
}

@objc(Zork) class Zork : Zilk {
    override var description : String {return "I am a Zork"}
}

Notice the use of the special @obj syntax to dictate the Objective-C munged name of these classes; that's crucial, because otherwise we don't know the munged string that designates each class.

Now we can use NSClassFromString to make the Zork class or the Zilk class, because we know we can type it as an NSObject and not crash later:

let aClass = NSClassFromString("Zork") as NSObject.Type
let anObject = aClass()
println(anObject) // "I am a Zork"

And it's reversible; println(NSStringFromClass(anObject.dynamicType)) also works.


Modern version:

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 10
    Upvote for the `@objc(ClassName)` bit. I knew about the `@objc` attribute but not that you could also give a hint about the class name. – Erik Jun 13 '14 at 02:23
  • 1
    Excellent solution that still works more or less as written 6 years on. Just a couple minor tweaks the playground asked for: `as! NSObject.Type` in the first line and `aClass.init()` in the second – Kaji Jun 01 '20 at 20:22
13

If I'm reading the documentation right, if you deal with instances and e.g. want to return a new instance of the same Type than the object you have been given and the Type can be constructed with an init() you can do:

let typeOfObject = aGivenObject.dynamicType
var freshInstance = typeOfObject()

I quickly tested it with String:

let someType = "Fooo".dynamicType
let emptyString = someType()
let threeString = someType("Three")

which worked fine.

monkeydom
  • 2,885
  • 1
  • 18
  • 12
  • 1
    Yep, `dynamicType` works as I expected there. However, I have been unable to compare types. The real big use is with generics, so I could have something like `Generic` and inside `if T is Double {...}`. It seems that is not possible unfortuantely. – Erik Jun 10 '14 at 01:12
  • 1
    @SiLo Did you ever find a way to ask in general whether two objects are of the same class? – matt Jun 17 '14 at 01:19
  • 1
    @matt Not elegantly, no I did not. However, I was able to create a `Defaultable` protocol which works similar to C#'s `default` keyword, and proper extensions for types like `String` and `Int`. By adding the generic constraint of `T:Defaultable`, I could check if the argument passed `is T.default()`. – Erik Jun 17 '14 at 15:06
  • 1
    @SiLo Clever; I'd like to see that code! I take it that this gets around the strange limitations on the use of "is". I've filed a bug on those limitations, and also on the general lack of class introspection. I ended up comparing strings using NSStringFromClass, but of course that only works for NSObject descendants. – matt Jun 17 '14 at 15:54
  • 1
    @matt Unfortunately it sounds more clever than it really is because you still have to do `value is String.default()`... etc, which you would just end up doing `value is String` instead. – Erik Jun 17 '14 at 16:10
13

In swift 3

object.dynamicType

is deprecated.

Instead use:

type(of:object)
J.beenie
  • 861
  • 10
  • 22
7

Swift implementation of comparing types

protocol Decoratable{}
class A:Decoratable{}
class B:Decoratable{}
let object:AnyObject = A()
object.dynamicType is A.Type//true
object.dynamicType is B.Type//false
object.dynamicType is Decoratable.Type//true

NOTE: Notice that it also works with protocols the object may or may not extend

Sentry.co
  • 5,355
  • 43
  • 38
1

Finally got something to work. Its a bit lazy but even the NSClassFromString() route did not work for me...

import Foundation

var classMap = Dictionary<String, AnyObject>()

func mapClass(name: String, constructor: AnyObject) -> ()
{
    classMap[name] = constructor;
}

class Factory
{
    class func create(className: String) -> AnyObject?
    {
        var something : AnyObject?

        var template : FactoryObject? = classMap[className] as? FactoryObject

        if (template)
        {
            let somethingElse : FactoryObject = template!.dynamicType()

            return somethingElse
        }

        return nil
    }
}


 import ObjectiveC

 class FactoryObject : NSObject
{
    @required init() {}
//...
}

class Foo : FactoryObject
{
    class override func initialize()
    {
        mapClass("LocalData", LocalData())
    }
    init () { super.init() }
}

var makeFoo : AnyObject? = Factory.create("Foo")

and bingo, "makeFoo" contains a Foo instance.

The downside is your classes must derrive from FactoryObject and they MUST have the Obj-C +initialize method so your class gets automagically inserted in the class map by global function "mapClass".

1

Here is another example showing class hierarchy implementation, similar to accepted answer, updated for the first release of Swift.

class NamedItem : NSObject {
    func display() {
        println("display")
    }

    required override init() {
        super.init()
        println("base")
    }
}

class File : NamedItem {
    required init() {
        super.init()
        println("folder")
    }
}

class Folder : NamedItem {
    required init() {
        super.init()
        println("file")
    }
}

let y = Folder.self
y().display()
let z = File.self
z().display()

Prints this result:

base
file
display
base
folder
display
possen
  • 8,596
  • 2
  • 39
  • 48
  • 2
    This technique does not work correctly if the variable is the superclass's Type. For example, given `var x: NamedItem.Type`, if I assign it `x = Folder.Type`, then `x()` returns a new `NamedItem`, not a `Folder`. This renders the technique useless for many applications. I consider this [a bug](http://openradar.appspot.com/radar?id=5796632348065792). – phatmann Nov 30 '14 at 10:13
  • 1
    Actually you can do what I think you want using this technique http://stackoverflow.com/questions/26290469/create-a-swift-object-from-a-dictionary/26290606#26290606 – possen Dec 02 '14 at 18:43