Swift言語NSClassFromString


83

Swift言語での反映を実現する方法は?

クラスをインスタンス化するにはどうすればよいですか

[[NSClassFromString(@"Foo") alloc] init];

1
ImplementationClass:NSObject.Type = NSClassFromString(className)as?NSObject.Type {ImplementationClass.init()}
Pushpa Raja

回答:


37

ここでよりハッキーな解決策:https//stackoverflow.com/a/32265287/308315

Swiftクラスの名前空間が変更されたため、「MyViewController」ではなく「AppName.MyViewController」になります。


XCode6-beta6 / 7以降非推奨

XCode6-beta3を使用して開発されたソリューション

Edwin Vermeerの回答のおかげで、次のようにして、SwiftクラスをObj-Cクラスにインスタンス化するための何かを構築することができました。

// 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
}

編集

純粋な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);
}

これが誰かに役立つことを願っています!


2
ベータ7以降、これは機能しなくなります。NSStringFromClassは、バンドル名とクラス名をドットで区切って返すようになりました。したがって、次のようなコードを使用できます: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

@KevinDelordコードであなたのテクニックを使用しました。ただし、アプリの名前にスペースが含まれている場合、appName変数は正しい値を返しません。それを修正する方法はありますか?Ex「App_Name」ではなく「AppName」
Loadex 2015年

countElements関数が見つかりません。これについて何か助けはありますか?
C0D3 2016

代わりにこれをclassStringNameに使用します:let classStringName = "_TtC(appName!.characters.count)(appName)(className.characters.count)(className)"
C0D3 2016

@ Loadex、実行可能ファイル名にスペースが含まれている場合、これは機能しません。また、スペースをアンダースコアに置き換える必要があります。この(やや紛らわしい)ブログ投稿で提案されました:medium.com/@maximbilan/…。投稿のコードは機能しましたが、実際の投稿では、コードとは異なるアプリ名とターゲット名の使用について説明していました。
stickj 2017

57

あなたは@objc(SwiftClassName)あなたの速いクラスの上に置かなければなりません。
お気に入り:

@objc(SubClass)
class SubClass: SuperClass {...}

2
NSClassFromString()関数には、@objc属性で指定された名前が必要です。
eonil 2014

1
驚くべきことに、私の精神的健康に大きな影響を与えるような小さなことです!
Adrian_H 2015年

クラス名の上で@objcを実行した後、NSClassFromString()の使用方法を誰かに示すことができますか?AnyClassを取得します!戻ってきた
Ryan Bobrowski 2015年

わたしにはできる。しかし@objc(SubClass)、なぜ機能するのかについて質問が@objc class SubClassありますが、そうではありませんか?
pyanfield 2015年

Swiftドキュメントの@pyanfield:「objc属性はオプションで識別子で構成される単一の属性引数を受け入れます。識別子はobjc属性が適用されるエンティティのObjective-Cに公開される名前を指定します。」したがって、違いは、SwiftサブクラスがObjective Cに表示される名前を取得する方法です。@objc class SubClassフォームでは、名前はサブクラス名と同じであることが暗示されています。そして、@objc(SubClass) class SubClassそれは直接指定された形式で。なんらかの理由で、コンパイラーは最初の形式ではそれ自体を理解できないと思います。
voiger 2018

56

これは、派生したUIViewControllerをクラス名で初期化する方法です。

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

詳細はこちら

iOS9の場合

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

11
アプリ名に「-」が含まれている場合は、「_」に置き換える必要があります
Zitao Xiong 2016

@RigelChen素晴らしいソリューションに感謝しますが、型(TestViewController)が必要で、毎回初期化したくない場合は、どうすればよいですか?
ArgaPK 2018年

@RyanHeitner素晴らしいソリューションに感謝しますが、型(TestViewController)が必要で、毎回初期化したくない場合は、どうすればよいですか?
ArgaPK 2018年

@argapシングルトンを作成し、それにアクセスします。letviewController = aClass.sharedInstance()
mahboudz19年

27

更新:ベータ6以降、NSStringFromClassは、バンドル名とクラス名をドットで区切って返します。つまり、MyApp.MyClassのようなものになります

Swiftクラスには、次の部分で構成される構築された内部名があります。

  • _TtCで始まります。
  • その後に、アプリケーション名の長さである番号が続きます。
  • 続いてアプリケーション名、
  • クラス名の長さである番号が後に続き、
  • その後にクラス名が続きます。

したがって、クラス名は_TtC5MyApp7MyClassのようになります。

次のコマンドを実行すると、この名前を文字列として取得できます。

var classString = NSStringFromClass(self.dynamicType)

更新Swift3では、これは次のように変更されました。

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

その文字列を使用して、以下を実行することにより、Swiftクラスのインスタンスを作成できます。

var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()

これはNSObjectクラスでのみ機能します。Swiftクラスについてはどうでしょうか。デフォルトでは、initメソッドがなく、プロトコルへのタイプキャストが機能していないようです。
ステファン

このコンストラクトを使用した私の場合、Swift 1.2 / XCode 6.3.1crash
Stephan

NSCoding、Printable、Hashable、Equatable、JSONをサポートするリフレクションクラスを作成しましたgithub.com/evermeer/EVReflection
Edwin Vermeer

上記の問題はSwift2.0で修正されています... initを呼び出す必要があるので私の答えを参照してください.... nsobjectype()だけが正しくなくなりました。
ステファン2015

1
let classString = NSStringFromClass(type(of:self))
Abhishek Thapliyal 2017年

11

それはほとんど同じです

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

このドキュメントを確認してください:

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


5
この関数はNSClassクラスでのみ機能し、Swiftクラスでは機能しません。NSClassFromString("String")を返しますがnil、返しNSClassFromString("NSString")ません。
Cezary Wojcik 2014年

私はコンピューターの前にいません...しかし、あなたは次のことを試すことができます: var myVar:NSClassFromString("myClassName")
gabuh 2014年

2
@CezaryWojcik:psStringはクラスではありません。それは構造体です
newacct 2014年

2
@newacct D'oh、あなたは正しい、私の悪い。しかし、とにかくすべてのSwiftクラスにもNSClassFromString戻りますnil
Cezary Wojcik 2014年

クラスに@objcアノテーションが付けられているか、NSObjectから継承している場合、そのクラスはObjective-Cオブジェクトシステムを使用し、NSClassFromString、メソッドスウィズリングなどに参加します。ただし、そうでない場合、クラスはSwiftコードの内部にあり、同じオブジェクトシステムを使用しないでください。完全には説明されていませんが、SwiftのオブジェクトシステムはObjective-Cよりも遅れが少ないようです。
ビル

9

オブジェクトを動的にインスタンス化することができました

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

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

(Obj-Cを使用せずに)AnyClassから作成する方法が見つかりませんでしたString。基本的に型システムを壊してしまうので、そうしてほしくないのではないかと思います。


1
この答えは、NSClassFromStringに関するものではありませんが、動的オブジェクトの初期化を行うSwift中心の方法を提供するための最良の答えだと思います。共有してくれてありがとう!
マットロング

@SulthanとMattにも、この応答が一番上にあるべき理由を強調してくれてありがとう!
イリアスカリム

7

swift2の場合、これをより迅速に行うための非常に単純な拡張機能を作成しました 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()
    }
}

私の場合、これを実行して、必要なViewControllerをロードします。

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)
}

6

これにより、インスタンス化するクラスの名前が取得されます。次に、Edwinsの回答を使用して、クラスの新しいオブジェクトをインスタンス化できます。

ベータ6の時点で_stdlib_getTypeName、変数のマングルされた型名を取得します。これを空の遊び場に貼り付けます。

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))")

出力は次のとおりです。

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

Ewan Swickのブログエントリは、これらの文字列を解読するのに役立ちます:http//www.eswick.com/2014/06/inside-swift/

たとえば_TtSi、Swiftの内部Int型を表します。


「次に、Edwinsの回答を使用して、クラスの新しいオブジェクトをインスタンス化できます。」私はその参照とは思わない。PureSwiftClassの回答作業。たとえば、デフォルトでは、このクラスにはinitメソッドがありません。
ステファン

6

Swift 2.0の場合(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)
}

5

xcode 7ベータ5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}

3

クラスからの文字列

let classString = NSStringFromClass(TestViewController.self)

または

let classString = NSStringFromClass(TestViewController.classForCoder())

文字列からUIViewControllerクラスを初期化します。

let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()

3

私はこのカテゴリをSwift3に使用しています。

//
//  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

    }

}

用途は次のとおりです。

func getViewController(fromString: String) -> UIViewController? {
    guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
        return nil
    }
    return viewController.init()
}

2

少なくとも現在のベータ版では、できないと言っているのは正しいと思います(2)。うまくいけば、これは将来のバージョンで変更されるものです。

を使用NSClassFromStringして型の変数を取得できますがAnyClass、Swiftにはそれをインスタンス化する方法がないようです。Objective Cへブリッジを使用してそこで実行するか、または-それが機能する場合は-switchステートメントの使用にフォールバックできます。


Swift 1.2 / XCode 6.3.1を使用しても、それは不可能と思われますか、それとも解決策を見つけましたか?
ステファン

2

どうやら、クラスの名前が実行時にしかわからない場合、Swiftでオブジェクトをインスタンス化することは(もう)不可能です。NSObjectのサブクラスにはObjective-Cラッパーが可能です。

少なくとも、Objective-Cラッパーなしで実行時に指定された別のオブジェクトと同じクラスのオブジェクトをインスタンス化できます(xCodeバージョン6.2-6C107aを使用)。

    class Test : NSObject {}
    var test1 = Test()
    var test2 = test1.dynamicType.alloc()

これは、クラス名からインスタンスを作成しません。
ステファン

あなたの答えからは明らかではありません、それは純粋なSwiftクラスでは機能していないようですよね?
ステファン

2

Swift 2.0(Xcode 7のbeta2でテスト済み)では、次のように機能します。

protocol Init {
  init()
}

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

確かに、から来るタイプはNSClassFromStringこのinitプロトコルを実装する必要があります。

私はそれが明確であり、期待してclassName、デフォルトでは「フー」だけではなく、あるクラスのOBJの-Cのランタイム名を含む文字列ですが、この議論は私見はあなたの質問の主要な話題ではありません。

デフォルトではすべてのSwiftクラスがinitメソッドを実装していないため、このプロトコルが必要です。


あなたが純粋なSwiftクラスを持っているなら、私の他の質問を見てください:stackoverflow.com/questions/30111659
Stephan

2

正しい呪文は...

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
}

...ここで、「p」は「パッケージ化された」を表します–明確な問題です。

しかし、AnyClassからTへのクリティカルキャストは現在コンパイラのクラッシュを引き起こしているため、その間にkの初期化を別のクロージャにバストする必要があります。これは正常にコンパイルされます。


2

異なるターゲットを使用していますが、この場合、swiftクラスが見つかりません。CFBundleNameをCFBundleExecutableに置き換える必要があります。警告も修正しました:

- (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);
}

2

解決策はこれほど単純ではありませんか?

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

// className = "MyApp.MyClass"

3
元の質問は、クラスからの文字列ではなく、文字列からのクラスが必要でした。
ライアン

1

Swift 2.0でも(おそらく以前?)dynamicTypeプロパティを使用してタイプに直接アクセスできます

すなわち

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)

出力

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

私のユースケースで動作します


1

これを試して。

let className: String = String(ControllerName.classForCoder())
print(className)

1

私はこのように実装しました、

if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
   ImplementationClass.init()
}

1

Swift 5、@ OndrejRafajのおかげで使いやすい

  • ソースコード:

    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
    
       }
    
    }
    
  • このように呼び出します:

    guard let ctrl = "ViewCtrl".controller else {
        return
    }
    //  ctrl do sth
    

0

ここに示されているページジャンプの例、希望があなたを助けることができます!

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)
}

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)
    }
}

このコードスニペットをありがとうございます。これは、限られた即時のヘルプを提供する可能性があります。適切な説明が大幅に長期的な価値を向上させるだろう示すことによって、なぜこれが問題に良い解決策であり、他の、同様の質問を将来の読者にそれがより便利になるだろう。あなたが行った仮定を含むいくつかの説明を追加するためにあなたの答えを編集してください。
iBug 2018

-6

これが良い例です:

class EPRocks { 
    @require init() { } 
}

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

var epawesome = EPAwesome.self(); 
print(epawesome.awesome);

1
これは、質問が求めていることを行っていません。
ThomasW 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.