Swift class introspection & generics
IntrospectionSwiftIntrospection Problem Overview
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 fromNSClassFromString
? - Is there a way to get
AnyClass
or otherwise type information strictly from a generic parameterT
? (Similar to C#'stypeof(T)
syntax)
Introspection Solutions
Solution 1 - Introspection
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:
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.
Solution 2 - Introspection
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
}
Solution 3 - Introspection
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.
Solution 4 - Introspection
In swift 3
object.dynamicType
is deprecated.
Instead use:
type(of:object)
Solution 5 - Introspection
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
Solution 6 - Introspection
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".
Solution 7 - Introspection
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