4

I have a nested class defined like this:

@objc class A { 
    @objc class B{

    } 
}

and I need to instantiate A.B using NSClassFromString. I was able to do it for the simple class A but when I attach to the NSClassFromString parameter the .B string it just returns nil.

NSClassFromString("\(appName).A") // works... 
NSClassFromString("\(appName).A.B") //doesn't work.

I suppose that since nested class are not available in Objective-c the NSClassFromString just doesn't work for nested classes... in that case is there another way to initialize a nested class from a string?

// EDIT

is curious how the inverse function NSStringFromClassreturns different format when executed for a standard class and a nested class:

"myApp.A" <---- STANDARD CLASS (A)
"_TtCC15myApp13A6B" <----- NESTED CLASS (A.B) 

As you can see the format is completely different. What is that _TtCC15? Why the "." has been removed? I suppose that passing the class in that format to NSClassFromString should work.

9
  • 1
    Try marking class B as @objc like so: @objc class B { }. Commented Jan 5, 2017 at 15:08
  • Already tried... I update the answer though, thanks for pointing it out! Commented Jan 5, 2017 at 15:37
  • Try to prefix class B with public keyword. Like this @objc public class B Commented Jan 5, 2017 at 15:46
  • @NSDmitry sadly it doesn't work. Commented Jan 5, 2017 at 15:55
  • @MatterGoal what error does compiler tell you? Commented Jan 5, 2017 at 16:03

1 Answer 1

3

I find that the following works in a playground (Xcode 8.2 / Swift 3):

// inheriting NSObject is required for `@objc`, at which point `@objc` is optional
class A: NSObject {
    class B: NSObject {
        override var description: String { return "foo" }
    }
}

let str = NSStringFromClass(A.B.self)
guard let anyClass = NSClassFromString(str)
    else { fatalError("no class") }
// cast to a type that defines `init()` so we can instantiate
guard let nsClass = anyClass as? NSObject.Type 
    else { fatalError("class isn't NSObject") }
// call `.init()`, not `nsClass()`; constructor syntax is for static types only
let instance = nsClass.init() 
print(instance) // -> "foo"

The oddball class "name" string is because the ObjC runtime doesn't understand nested classes (or other kinds of types that Swift can define but ObjC can't) -- within Swift itself, such mangled names are how types, functions and such get uniquely defined. (For example, name mangling is also how function overloading works: func foo() and func foo(num: Int) -> Bool have different mangled names internally.)

The bridging machinery can still be made to dynamically resolve a class given its properly mangled Swift name, but Swift name mangling is an implementation detail and subject to change. (At least until Swift 4 locks down the ABI.) So it's safe to pass the result of NSStringFromClass to NSClassFromString within the same program, but not safe to (e.g.) record a mangled name once in testing, ship that mangled name as a string literal or resource in your app, and expect it to work with NSClassFromString later.

Instead, if you want your nested class (or any other Swift-defined class) to have a reliably known name for use in the ObjC runtime, give it an @objc(name) (as described in the docs):

@objc class A: NSObject {
    @objc(A_B) class B: NSObject { /*...*/ }
}
guard let anyClass = NSClassFromString("A_B")
    else { fatalError("no class") }

(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.)

Sign up to request clarification or add additional context in comments.

1 Comment

Using ObjC name did the trick! About the rest of your answer, great explanation!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.