SwiftedMind
SwiftedMind

Reputation: 4287

NSClassFromString returning nil for nested class

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.

Upvotes: 4

Views: 536

Answers (1)

Charles Srstka
Charles Srstka

Reputation: 17040

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.

Upvotes: 4

Related Questions