4

I want to instantiate a class by using a string but I'm having problems. That's what I try to do:

let aClass = NSClassFromString("MyApp.Outer.Inner") as! SomeProtocol.Type
....
public class Outer {
    public class Inner: SomeProtocol {
        init() {...}
    }
}

That's crashing (because NSClassFromString returns nil

But that's working:

let aClass = NSClassFromString("MyApp.Inner") as! SomeProtocol.Type
....
public class Inner: SomeProtocol {
    init() {...}
}

Doesn't NSClassFromString work for nested classes?


Background: I have many "inner" classes that I use as templates to create objects (instead of storing them in .plist files, which is kind of annoying). I want to encapsulate them in an "Outer" class to have them better organised.

Thanks for any help :)

EDIT: I saw the question that this seems to be a duplicate of. But that's not a viable solution:

(Note this snippet won't run by itself -- the class A.B has to be referenced at least once somewhere in your process in order to be registered with the ObjC runtime. This could be as simple as putting let t = A.B.self somewhere in your app's startup code.)

I'm doing this approach with NSClassFromString because I want to avoid adding too much stuff for every inner class I create. I don't want to constantly add this to my startup code.

SwiftedMind
  • 3,701
  • 3
  • 29
  • 63
  • I'm wondering since `NSClassFromString` is a Obj-C legacy thing, it can't do something thats uniquely Swifty (nested classes) ? – Warren Burton Aug 16 '17 at 17:55
  • "I'm doing this approach with NSClassFromString because I want to avoid adding too much stuff for every inner class I create. I don't want to constantly add this to my startup code" I'm sorry, but in my view resorting to NSClassFromString for this purpose is a Bad Smell. You should rethink your entire strategy (in my view). – matt Aug 16 '17 at 19:15
  • That's the best way I could think of trying to access many classes in the most dynamic way possible. I use these in the following way: I have a game where you can unlock things (new items for example). These things have attributes (level, name, ...) that I need to grab from somewhere in order to create a new database object. I grab these attributes from a "template class" (the inner classes I talked about) that have these attributes. I couldn't think of a better way to access the classes, than using a string and NSClassFromString – SwiftedMind Aug 16 '17 at 19:51

1 Answers1

4

If you run this code:

import Foundation

class Outer: NSObject {
    class Inner: NSObject {}
}

print(NSStringFromClass(Outer.Inner.self))

You will get something like the following:

_TtCC8Untitled5Outer5Inner

As you can see, embedded classes have their names mangled differently from how you'd expect. I believe the mangling format is undocumented, as well, so you can't rely on this not changing in some future Swift release.

If you need to be able to access a class using its name in the Objective-C runtime, you can use the @objc attribute to give it a custom Objective-C name. Unfortunately, the @objc keyword doesn't allow dots, but you can use underscores for a similar effect:

class Outer: NSObject {
    @objc (Outer_Inner) class Inner: NSObject {}
}

EDIT: I should clarify that even with the @objc keyword specified, the inner class will still need to be accessed by Swift code before it will be available to the Objective-C runtime, and thus before functions like NSClassFromString() will work.

Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
  • Yeah, I know that (my question got marked as duplicate because there is a question with this exact answer as well). But this just moves my problem to another point. This way, I still have to manually add a line for every Inner class I write – SwiftedMind Aug 16 '17 at 19:47
  • You have to do that anyway, though, because as far as the Objective-C runtime, and thus, functions like `NSClassFromString()` are concerned, the inner class doesn't exist until something on the Swift side references it. – Charles Srstka Aug 16 '17 at 19:50
  • Hm, sound logical. Though, then I have to find a complete different approach because this way it isn't solving anything (I only do this to create these classes more conveniently) – SwiftedMind Aug 16 '17 at 19:52