Creating a class from a string in Swift

One thing that Swift does not do very well is dynamism. That is because of the fact that it is a type safe language. So if you were to create a class from a classname i.e. a String, you would get different types of classes depending on the class name. So having a single variable that would cater to each and every class would be very inconvenient and impossible. This article explores the way that you could have a factory function to create an instance of a class from the given classname.

Objective-C Functions

Objective-C offers two functions, NSStringFromClass and NSClassFromString. These are the core to what we want in this article. Let’s explore the use of these first.

class test {
 var theName:String = "Nothing"
}

println("\(NSStringFromClass(test))")

This would have the output of something like __lldb_expr_635.test If this was run in an application, the results would be different.

Now let’s look at the reverse, creating a class using the name of the class as a string.

var theClass = NSClassFromString("test")

You will notice that it returns nil. You would rather expect that the result be the class or in Playgrounds, ( ExistentialMetatype ).

The reason is very simple, if you compare the result of the classname returned from the function NSStringFromClass it is not the same as test.

Option #1

The first option to make this work is if the class was an Objective-C type class. There IS a difference between an Objective-C class and a Swift class.

@objc(test)
class test {
  var theName:String = "The test class"
}

var _theClass = NSClassFromString("test")

This time around you would notice that the result from the function is no longer nil but an instance of test.

That is one way to allow Objective-C functions access Swift functions.

Option #2

The other option if you do not want to mark the class with Objective-C then you could create the string properly, which in case of Swift is in two parts, the first identifies the module and the second the classname, both separated with a period. Like we saw in the first example, when we used NSStringFromClass , we got __lldb_expr_635.test

So for this to work, you need to construct the string appropriately, using the Modulename.classname , this will work fine as we can see in the preceding code

class test {
  var theName:String = "Unknown"
}

var className = NSStringFromClass("test")
// This is the actual name of the class in Swift format
// like we discussed earlier ModuleName.Classname

var theClass = NSClassFromString(className)
// This is the class constructed with this function

One additional thing to note here is that since the functions will return a class based on the string or nil, thereby the return type is AnyObject!

Casting the variable

When working with Swift, you cannot access the members of the class if it is left as AnyObject! as it does not have any of the member functions or properties that you want to access. You need to cast the variable appropriately if to access it.

The problem with this is if you know how to cast the variable, then you wouldn’t want to use these functions in the first place anyways.

Closing thoughts

While it seems a bit of a let down in comparison to Objective-C where even if it is nil, it would be fine as compared to swift. So for now using a Swift solution does not work very well. The other option is to simply have a wrapper around the Objective-C code in a bridging header file. However swift would still have the issue of casting the AnyObject! returned to the appropriate class.

While some developers have tried to use the dynamic property to create the class as in the following code,

class test {
  var theName:String = "Unknown"
}

var instance: AnyObject! = nil
let classInst = NSClassFromString("test") as NSObject.Type
instance = classInst() // create the instance from this class

Lastly, the way you can use this with Swift is if you have a class hierarchy where the base class has the methods and properties that you would need. Since you cast the AnyObject! to the base class, it would work for all the classes that inherit from the base class.

Leave your comments or your thoughts if there is anything that is missed or needs to be added.

Views All Time
Views All Time
25441
Views Today
Views Today
1
Posted in Uncategorized and tagged , , , , .

2 Comments

  1. the result from NSClassFromString() doesn’t seem to be usable.

    the code:
    let createdClass = NSClassFromString(“UIView”)
    let objectFromCreatedClass: createdClass // !! ERROR!!

    produces the error: “Use of undeclared type ‘createdClass’

    NSClassFromString clearly does not return a usable Class Name, since the following line is not valid:
    let myObject = NSClassFromString(“UIView”)

    I tried the following code as well without success: (Swift 2.0 changed a couple things in syntax)
    extension UIView { var test: String { return “test” } }
    let myClass = NSClassFromString(“UIView”) as! UIView // also tried with NSObject.Type
    let myObject = classInst.init()
    myObject.test // ERROR: ‘NSObject does not have a member named ‘test’

    I am really puzzled at how absurdly difficult it is just to GET an object’s Class. It seems really RETARDED from the part of Apple

  2. Hi Hikarus,
    I am avoiding Swift 2.0 on my main machine waiting for Swift 2.0 to become standard, so this is a Swift 1.2 response.

    Firstly, this dilema is only because of the fact that there is no reflection available (Swift 2.0 has that). So as Apple bind more of the Objective-C Runtime functionality to Swift, this and more would be available. Currently, you can get the IMP (implementation) but cannot invoke it directly.

    The NSClassFromString works, try this in playgrounds if you want,

    let createdClass = NSClassFromString("UIView") as! UIView.Type // This creates an UIView
    let theView:UIView = createdClass() // Should now give you a new object
    

    You can check that by printing the value of theView, then assign the following (for example)

    theView.frame = CGRectMake(0, 0, 100, 100)
    theView.backgroundColor = .redColor()
    

    and print the values again, you will notice that the frame and the backgroundColor are applied to the object.

    Yes, I agree with you that it is not as yet easily and dynamically posible to create objects on the fly.

Comments are closed.