89

How to achieve reflection in Swift Language?

How can I instantiate a class

[[NSClassFromString(@"Foo") alloc] init];
Selvin
  • 12,333
  • 17
  • 59
  • 80
  • 1
    Try like this, if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{ ImplementationClass.init() } – Pushpa Raja Aug 31 '16 at 10:39

25 Answers25

59

You must put @objc(SwiftClassName) above your swift class.
Like:

@objc(SubClass)
class SubClass: SuperClass {...}
Mazyod
  • 22,319
  • 10
  • 92
  • 157
klaus
  • 591
  • 3
  • 2
  • 2
    `NSClassFromString()` function needs name specified by the `@objc` attribution. – eonil Sep 09 '14 at 11:53
  • 1
    Amazing, such a small thing with such a huge impact on my mental wellbeing! – Adrian_H Mar 31 '15 at 21:22
  • 1
    can someone show how to use NSClassFromString() after they do the @objc thing above their class name. I just get AnyClass! returned – Ryan Bobrowski Jun 05 '15 at 23:56
  • It works for me. But I have a question about why `@objc(SubClass)` works, but `@objc class SubClass` not? – pyanfield Oct 09 '15 at 09:33
  • 1
    @pyanfield from the Swift doc: "The objc attribute optionally accepts a single attribute argument, which consists of an identifier. The identifier specifies the name to be exposed to Objective-C for the entity that the objc attribute applies to." So, the difference is how our Swift subclass gets the name it will be visible for Objective C. In the `@objc class SubClass` form the name is implied to be the same as the SubClass name. And in the `@objc(SubClass) class SubClass` form it's specified directly. I guess the compiler just can't figure it out by itself in the first form for some reason. – voiger Dec 26 '18 at 07:33
59

This is the way I init derived UIViewController by class name

var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()

More information is here

In iOS 9

var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Ryan Heitner
  • 13,119
  • 6
  • 77
  • 119
Rigel Chen
  • 861
  • 7
  • 14
  • 11
    If the app name contain "-", it should be replaced by "_" – Zitao Xiong Jun 29 '16 at 04:30
  • @RigelChen nice solution thanks but if we just need type (TestViewController) and do not want to initialize it every time then what should we do? – ArgaPK Jan 02 '18 at 12:07
  • @RyanHeitner nice solution thanks but if we just need type (TestViewController) and do not want to initialize it every time then what should we do? – ArgaPK Jan 02 '18 at 12:12
  • @argap Create a singleton, and access it: let viewController = aClass.sharedInstance() – mahboudz Mar 12 '19 at 17:31
37

Less hacky solution here: https://stackoverflow.com/a/32265287/308315

Note that Swift classes are namespaced now so instead of "MyViewController" it'd be "AppName.MyViewController"


Deprecated since XCode6-beta 6/7

Solution developed using XCode6-beta 3

Thanks to the answer of Edwin Vermeer I was able to build something to instantiate Swift classes into an Obj-C class by doing this:

// swift file
// extend the NSObject class
extension NSObject {
    // create a static method to get a swift class for a string name
    class func swiftClassFromString(className: String) -> AnyClass! {
        // get the project name
        if  var appName: String? = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as String? {
            // generate the full name of your class (take a look into your "YourProject-swift.h" file)
            let classStringName = "_TtC\(appName!.utf16count)\(appName)\(countElements(className))\(className)"
            // return the class!
            return NSClassFromString(classStringName)
        }
        return nil;
    }
}

// obj-c file
#import "YourProject-Swift.h"

- (void)aMethod {
    Class class = NSClassFromString(key);
    if (!class)
        class = [NSObject swiftClassFromString:(key)];
    // do something with the class
}

EDIT

You can also do it in pure obj-c:

- (Class)swiftClassFromString:(NSString *)className {
    NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
    NSString *classStringName = [NSString stringWithFormat:@"_TtC%d%@%d%@", appName.length, appName, className.length, className];
    return NSClassFromString(classStringName);
}

I hope this will help somebody !

Community
  • 1
  • 1
Kevin Delord
  • 2,498
  • 24
  • 22
  • 2
    Starting from beta 7 this won't work anymore.NSStringFromClass will now just return your bundle name plus classname separated by a dot. So you could use code like: var appName: String = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as String? let classStringName: String = NSStringFromClass(theObject.dynamicType) return classStringName.stringByReplacingOccurrencesOfString(appName + ".", withString: "", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil) – Edwin Vermeer Sep 04 '14 at 06:30
  • @KevinDelord I used your technique in my code. But the appName variable does not return the correct value when the app has a space in his name. Any idea how to fix it ? Ex "App Name" instead of "App_Name" – Loadex Jan 14 '15 at 16:23
  • Can't find the countElements function. Any help on this? – C0D3 Jul 13 '16 at 20:27
  • Using this for classStringName instead: let classStringName = "_TtC\(appName!.characters.count)\(appName)\(className.characters.count)\(className)" – C0D3 Jul 13 '16 at 20:34
  • @Loadex, This doesn't works if your executable name has a space in it. You need to also replace the spaces with underscores. It was suggested in this (somewhat confusing) blog post: medium.com/@maximbilan/…. The code in the post worked, but the actual post talked about using appname and target name which was different from what their code did. – stuckj Aug 29 '17 at 19:24
  • Thank you very much for reminding that Swift classes are namespaced. I only lost 15 minutes with it instead of hours + my nerves thanks to you :D – Lord Zsolt Feb 15 '18 at 14:03
27

UPDATE: Starting with beta 6 NSStringFromClass will return your bundle name plus class name separated by a dot. So it will be something like MyApp.MyClass

Swift classes will have a constructed internal name that is build up of the following parts:

  • It will start with _TtC,
  • followed by a number that is the length of your application name,
  • followed by your application name,
  • folowed by a number that is the length of your class name,
  • followed by your class name.

So your class name will be something like _TtC5MyApp7MyClass

You can get this name as a string by executing:

var classString = NSStringFromClass(self.dynamicType)

Update In Swift 3 this has changed to:

var classString = NSStringFromClass(type(of: self))

Using that string, you can create an instance of your Swift class by executing:

var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
Edwin Vermeer
  • 13,017
  • 2
  • 34
  • 58
11

It's almost the same

func NSClassFromString(_ aClassName: String!) -> AnyClass!

Check this doc:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString

gabuh
  • 1,166
  • 8
  • 15
  • 5
    That function only works with `NSClass` classes, not Swift classes. `NSClassFromString("String")` returns `nil`, but `NSClassFromString("NSString")` does not. – Cezary Wojcik Jun 04 '14 at 07:41
  • I'm not in front of the computer... but you could try with: `var myVar:NSClassFromString("myClassName")` – gabuh Jun 04 '14 at 10:24
  • 2
    @CezaryWojcik: p.s. `String` is not a class; it is a struct – newacct Jun 05 '14 at 00:39
  • 2
    @newacct D'oh, you're right, my bad. But `NSClassFromString` returns `nil` for all Swift classes as well anyhow. – Cezary Wojcik Jun 09 '14 at 03:18
  • If a class is annotated @objc or inherits from NSObject, then it uses the Objective-C object system and participates in NSClassFromString, method swizzling, etc. However, if it doesn't, then the class is internal to your Swift code and doesn't use that same object system. It hasn't been fully described, but Swift's object system seems to be less late-bound than Objective-C's. – Bill Jun 09 '14 at 12:18
9

I was able to instantiate an object dynamically

var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()

if let testObject = instance as? TestObject {
    println("yes!")
}

I haven't found a way to create AnyClass from a String (without using Obj-C). I think they don't want you to do that because it basically breaks the type system.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    I think this answer, while not about NSClassFromString, is the best one here for giving a Swift-centric way to do dynamic object initialization. Thanks for sharing! – Matt Long Oct 03 '14 at 14:27
  • Thanks to @Sulthan and Matt as well for highlighting why this response ought to be on top! – Ilias Karim Mar 01 '18 at 19:24
7

For swift2, I created a very simple extension to do this more quickly https://github.com/damienromito/NSObject-FromClassName

extension NSObject {
    class func fromClassName(className : String) -> NSObject {
        let className = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String + "." + className
        let aClass = NSClassFromString(className) as! UIViewController.Type
        return aClass.init()
    }
}

In my case, i do this to load the ViewController I want:

override func viewDidLoad() {
    super.viewDidLoad()
    let controllers = ["SettingsViewController", "ProfileViewController", "PlayerViewController"]
    self.presentController(controllers.firstObject as! String)

}

func presentController(controllerName : String){
    let nav = UINavigationController(rootViewController: NSObject.fromClassName(controllerName) as! UIViewController )
    nav.navigationBar.translucent = false
    self.navigationController?.presentViewController(nav, animated: true, completion: nil)
}
Damien Romito
  • 9,801
  • 13
  • 66
  • 84
6

This will get you the name of the class that you want to instantiate. Then you can use Edwins answer to instantiate a new object of your class.

As of beta 6 _stdlib_getTypeName gets the mangled type name of a variable. Paste this into an empty playground:

import Foundation

class PureSwiftClass {
}

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")

The output is:

TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS

Ewan Swick's blog entry helps to decipher these strings: http://www.eswick.com/2014/06/inside-swift/

e.g. _TtSi stands for Swift's internal Int type.

Community
  • 1
  • 1
Klaas
  • 22,394
  • 11
  • 96
  • 107
  • "Then you can use Edwins answer to instantiate a new object of your class." I don't think that the ref. answer work for PureSwiftClass. e.g by default this class has no init method. – Stephan Apr 26 '15 at 14:22
6

In Swift 2.0 (tested in the Xcode 7.01) _20150930

let vcName =  "HomeTableViewController"
let ns = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String

// Convert string to class
let anyobjecType: AnyObject.Type = NSClassFromString(ns + "." + vcName)!
if anyobjecType is UIViewController.Type {
// vc is instance
    let vc = (anyobjecType as! UIViewController.Type).init()
    print(vc)
}
Stickley
  • 4,561
  • 3
  • 30
  • 29
Go Let
  • 61
  • 1
  • 1
5

xcode 7 beta 5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}
December
  • 584
  • 5
  • 10
3

string from class

let classString = NSStringFromClass(TestViewController.self)

or

let classString = NSStringFromClass(TestViewController.classForCoder())

init a UIViewController class from string:

let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Binglin
  • 74
  • 5
3

I am using this category for Swift 3:

//
//  String+AnyClass.swift
//  Adminer
//
//  Created by Ondrej Rafaj on 14/07/2017.
//  Copyright © 2017 manGoweb UK Ltd. All rights reserved.
//

import Foundation


extension String {

    func convertToClass<T>() -> T.Type? {
        return StringClassConverter<T>.convert(string: self)
    }

}

class StringClassConverter<T> {

    static func convert(string className: String) -> T.Type? {
        guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String else {
            return nil
        }
        guard let aClass: T.Type = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
            return nil
        }
        return aClass

    }

}

The use would be:

func getViewController(fromString: String) -> UIViewController? {
    guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
        return nil
    }
    return viewController.init()
}
Ondrej Rafaj
  • 4,342
  • 8
  • 42
  • 65
2

I think I'm right in saying that you can't, at least not with the current beta (2). Hopefully this is something that will change in future versions.

You can use NSClassFromString to get a variable of type AnyClass but there appears to be no way in Swift to instantiate it. You can use a bridge to Objective C and do it there or -- if it works in your case -- fall back to using a switch statement.

Stephen Darlington
  • 51,577
  • 12
  • 107
  • 152
2

Apparently, it is not possible (anymore) to instantiate an object in Swift when the name of the class is only known at runtime. An Objective-C wrapper is possible for subclasses of NSObject.

At least you can instantiate an object of the same class as another object given at runtime without an Objective-C wrapper (using xCode Version 6.2 - 6C107a):

    class Test : NSObject {}
    var test1 = Test()
    var test2 = test1.dynamicType.alloc()
seb
  • 196
  • 2
  • 8
2

In Swift 2.0 (tested in the beta2 of Xcode 7) it works like this:

protocol Init {
  init()
}

var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()

For sure the type coming from NSClassFromString have to implement this init protocol.

I expect it is clear, className is a String containing the Obj-C runtime name of the class which is by default NOT just "Foo", but this discussion is IMHO not the major topic of your question.

You need this protocol because be default all Swift classes don't implement an init method.

Stephan
  • 4,263
  • 2
  • 24
  • 33
  • If you have pur Swift classes see my other questions: http://stackoverflow.com/questions/30111659/ – Stephan Jul 02 '15 at 08:07
2

Looks like the correct incantation would be...

func newForName<T:NSObject>(p:String) -> T? {
   var result:T? = nil

   if let k:AnyClass = NSClassFromString(p) {
      result = (k as! T).dynamicType.init()
   }

   return result
}

...where "p" stands for "packaged" – a distinct issue.

But the critical cast from AnyClass to T currently causes a compiler crash, so in the meantime one must bust initialization of k into a separate closure, which compiles fine.

rory
  • 21
  • 2
2

I use different targets, and in this case the swift class is not found. You should replace CFBundleName with CFBundleExecutable. I also fixed the warnings:

- (Class)swiftClassFromString:(NSString *)className {
    NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"];
    NSString *classStringName = [NSString stringWithFormat:@"_TtC%lu%@%lu%@", (unsigned long)appName.length, appName, (unsigned long)className.length, className];
    return NSClassFromString(classStringName);
}
Calin Drule
  • 2,899
  • 1
  • 15
  • 12
2

Isn't the solution as simple as this?

// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)

// className = "MyApp.MyClass"
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
1

Also in Swift 2.0 (possibly before?) You can access the type directly with the dynamicType property

i.e.

class User {
    required init() { // class must have an explicit required init()
    }
    var name: String = ""
}
let aUser = User()
aUser.name = "Tom"
print(aUser)
let bUser = aUser.dynamicType.init()
print(bUser)

Output

aUser: User = {
  name = "Tom"
}
bUser: User = {
  name = ""
}

Works for my use case

apocolipse
  • 619
  • 6
  • 11
1

Try this.

let className: String = String(ControllerName.classForCoder())
print(className)
Himanshu
  • 2,832
  • 4
  • 23
  • 51
1

I have implemented like this,

if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
   ImplementationClass.init()
}
Pushpa Raja
  • 642
  • 6
  • 17
1

Swift 5, easy to use, thanks to @Ondrej Rafaj's

  • Source code:

    extension String {
         fileprivate
         func convertToClass<T>() -> T.Type? {
              return StringClassConverter<T>.convert(string: self)
         }
    
    
        var controller: UIViewController?{
              guard let viewController: UIViewController.Type = convertToClass() else {
                return nil
            }
            return viewController.init()
        }
    }
    
    class StringClassConverter<T> {
         fileprivate
         static func convert(string className: String) -> T.Type? {
             guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String, let aClass = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
                 return nil
            }
             return aClass
    
       }
    
    }
    
  • Call like this:

    guard let ctrl = "ViewCtrl".controller else {
        return
    }
    //  ctrl do sth
    
black_pearl
  • 2,549
  • 1
  • 23
  • 36
0

A page jump example shown here, the hope can help you!

let vc:UIViewController = (NSClassFromString("SwiftAutoCellHeight."+type) as! UIViewController.Type).init()
self.navigationController?.pushViewController(vc, animated: true)

// Click the Table response
tableView.deselectRow(at: indexPath, animated: true)
let sectionModel = models[(indexPath as NSIndexPath).section]
var className = sectionModel.rowsTargetControlerNames[(indexPath as NSIndexPath).row]
className = "GTMRefreshDemo.\(className)"
if let cls = NSClassFromString(className) as? UIViewController.Type {
   let dvc = cls.init()
   self.navigationController?.pushViewController(dvc, animated: true)
}
Tim
  • 1,528
  • 1
  • 11
  • 8
0

Swift3+

extension String {

    var `class`: AnyClass? {

        guard
            let dict = Bundle.main.infoDictionary,
            var appName = dict["CFBundleName"] as? String
            else { return nil }

        appName.replacingOccurrences(of: " ", with: "_")
        let className = appName + "." + self
        return NSClassFromString(className)
    }
}
SeRG1k
  • 121
  • 2
  • 5
  • Thank you for this code snippet, which might provide some limited, immediate help. A [proper explanation would greatly improve its long-term value](//meta.stackexchange.com/q/114762/350567) by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – iBug Feb 19 '18 at 14:03
-6

Here is a good example:

class EPRocks { 
    @require init() { } 
}

class EPAwesome : EPRocks { 
    func awesome() -> String { return "Yes"; } 
}

var epawesome = EPAwesome.self(); 
print(epawesome.awesome);
EvilPenguin
  • 535
  • 6
  • 9