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!
Pingback: Dew Drop – June 5, 2014 (#1791) | Morning Dew
Pingback: Dew Drop – June 6, 2014 (#1792) | Morning Dew
Do I understand correctly that you cannot access your own ObjC code from a Playground? This would mean we cannot use your (elegant) work-around in Playground mode?
Charles, it appears that you are correct. Hopefully Apple addresses this shortcoming soon.
Pingback: Learning Swift | ZX81.org.uk
Pingback: Michael Tsai - Blog - Swift Links
Pingback: Michael Tsai - Blog - Instantiating Classes by Name in Swift
But see my discussion here: http://stackoverflow.com/a/24196632/341994 Basically the problem is merely that you don’t _know_ Swift’s “name” for your class – unless you dictate that name using the `@objc(UseThisName)` syntax. Once you do that, it’s just a matter of making sure that Swift is satisfied that the initializer you’re using is one that this class can respond to. So yes, I’m certainly using `NSClassFromString()`, and yes, this must be an Objective-C class (descend from NSObject), but there is no need to “hop into the parallel universe”: Cocoa and NSClassFromString will always exist, and the code itself is pure Swift.
Matt, I read your post at stackoverflow and tried to do exactly what you described in a playground. I wasn’t able to get it to work — does your example work in playground mode? (btw, I would have asked this at stackoverflow, but I don’t have enough credit)
Playgrounds don’t work at all for me, so I didn’t even try it. This is in my Swift rewrite of an app which, like Josh’s, relies heavily on deciding by their string name what classes to instantiate. It’s working in Swift. (Now, however, I intend to remove that behavior; in Swift there’s usually a Swiftier way.)
Very interesting! Thanks for sharing that here, Matt.
Is the lookup via class name really required? Why don’t you just have a dictionary mapping from the class names you want to use to the class objects? Or you could even create a enum which has a method to return the class for the particular values.
If that only helps so far you may also need convenience class constructors too on Person.
I haven’t written any Swift – but it seems like to me relying on a string to be converted to a class may be going away on purpose. Your class name strings have no compiler checking. Doing something as simple as passing Friend.self instead may be better.
That is what I meant when I said “Now, however, I intend to remove that behavior; in Swift there’s usually a Swiftier way.” I agree with both of you (Josh and Patrick). On the one hand, at first, I just wanted to rewrite my existing code in Swift, using all NSObject-compatible classes and doing exactly what the Objective-C code had done; thus, I explored how to call NSStringFromClass and NSClassFromString. On the other hand, when that’s all done, I will do a second pass where I completely rearchitect, adopting Swift enums, structs, and non-NSObject classes, and using Swift idioms and techniques. It will certainly turn out that I can do without NSStringFromClass and NSClassFromString now that we have Swift, but it is also true that at the time I used them, in the language I was using, for the purpose I was using them for, they were the neatest and cleanest way.
It’s not required, but neither is having classes. After having had the ability to easily instantiate classes by name, and basing class designs on that concept, I balk at giving it up.
Hi. Great post, thanks!
I’ve been working on a model de/serializing library and I’ve gotten a few tests to work with some basic instantiating and reflecting of runtime object. I have an Obj-C version of this I’ve been using on projects for a few years and so, I decide to see how it would work in Swift. Not completely, what you’re talking about, but some of it relates.
You can check out the repo if you’re interested. https://github.com/Jasonbit/JBModel-Swift
I have a few tests passing now and I’ll be adding more as i go.
Thanks for the great post. It helped me alot.