This article demonstrates how to create a Swift class instance based solely on the class name and possibly an argument for its initializer method. It relies on Objective-C interop to perform the actual object creation, which is hidden behind a generic Swift convenience class called ObjectFactory.
The source code reviewed below can be found on Github:
This code was compiled and tested against Xcode 6 Beta 5.
Over the years I’ve written several components in Objective-C that I use in almost all of my iOS projects. A few of them critically depend on the ability to instantiate a class based on a string that contains the class’s name. Those strings might come from a configuration file or be the result of concatenating strings based on a class naming convention.
In Objective-C that is easy as pie, just call NSClassFromString(aClassName) and then alloc/init the Class it returns. However, now that Swift is destined to become the lingua franca of iOS I decided to investigate how one might implement these reusable components in Swift. Instantiating classes by name turned out to be a stumbling block, as it appears that Swift does not yet support this.
Disclaimer: I’ve only been studying Swift since it was publicly debuted two days ago. This entire blog post might be obviated by some utility function I haven’t been able to find or will soon be added to the API in the first official Swift release.
My class named ObjectFactory creates Swift class instances based on the class name. It supports creating objects with either a parameterless initializer or an initializer that takes exactly one parameter. The class could easily be modified to support initializers with more parameters, if necessary.
There is a restriction that ObjectFactory imposes on the classes it can instantiate: they must derive (directly or indirectly) from NSObject. By default Swift classes do not derive from NSObject. There is no harm in making a Swift class derive from NSObject, though it might not always be possible based on a project’s specific class inheritance needs. The reason why this restriction exists will become apparent later in this article when I explain how ObjectFactory works.
Suppose your application has the following three classes: Person, Friend, and Stranger. The latter two are Person subclasses, and Person is an NSObject.
If you need to create instances of Stranger and Friend based on the name of their classes you could use one of ObjectFactory’s createInstance methods. The simpler version of the method just requires you to pass it a class name that is prefixed by its enclosing namespace, such as “MyApp.SomeClassName”. The other version of the createInstance method allows you to specify which single-parameter initializer method to use and what value its argument should be.
Here is the output of this program:
Created a Stranger Friend name = Steve
How it works
As I mentioned before, there does not appear to be a way to instantiate a class by name in the Swift API. I was able to get an object that represents a class (named AnyClass), but there was no way to get from there to a live instance of that class. So instead, I hopped into the parallel universe of Objective-C to create an object, which then gets returned back to Swift. This is why ObjectFactory can only work with NSObject subclasses; I’m relying on Objective-C to alloc/init a new object, and those methods are defined by NSObject.
Here is the ObjectFactory class, which is just a Swift convenience API over the Objective-C code that does the real work.
ObjectFactory relies on OBJCObjectFactory to create and initialize new objects. Once you’ve added that Objective-C class’s header and implementation files to your project, Xcode will create a “bridging header” file for you, which allows you to expose Objective-C classes to your Swift code. Be sure to add this to your project’s bridging header file:
This class’s header file is shown below.
The implementation of OBJCObjectFactory is short and straightforward, if you know Objective-C that is! Note that the single-parameter initializer selector is invoked through a pointer to a function, instead of the standard performSelector:withObject: method, because it eliminates a compiler warning and establishes a pattern that can be extended to include an arbitrary number of initializer arguments.
Feel free to use ObjectFactory in your apps, if you need it. Please let me know if you find a pure Swift way to do this!