Swift言語での反映を実現する方法は?
クラスをインスタンス化するにはどうすればよいですか
[[NSClassFromString(@"Foo") alloc] init];
回答:
ここでよりハッキーな解決策: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);
}
これが誰かに役立つことを願っています!
あなたは@objc(SwiftClassName)
あなたの速いクラスの上に置かなければなりません。
お気に入り:
@objc(SubClass)
class SubClass: SuperClass {...}
NSClassFromString()
関数には、@objc
属性で指定された名前が必要です。
@objc(SubClass)
、なぜ機能するのかについて質問が@objc class SubClass
ありますが、そうではありませんか?
@objc class SubClass
フォームでは、名前はサブクラス名と同じであることが暗示されています。そして、@objc(SubClass) class SubClass
それは直接指定された形式で。なんらかの理由で、コンパイラーは最初の形式ではそれ自体を理解できないと思います。
これは、派生した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()
更新:ベータ6以降、NSStringFromClassは、バンドル名とクラス名をドットで区切って返します。つまり、MyApp.MyClassのようなものになります
Swiftクラスには、次の部分で構成される構築された内部名があります。
したがって、クラス名は_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()
それはほとんど同じです
func NSClassFromString(_ aClassName: String!) -> AnyClass!
このドキュメントを確認してください:
NSClass
クラスでのみ機能し、Swiftクラスでは機能しません。NSClassFromString("String")
を返しますがnil
、返しNSClassFromString("NSString")
ません。
var myVar:NSClassFromString("myClassName")
String
はクラスではありません。それは構造体です
NSClassFromString
戻りますnil
。
オブジェクトを動的にインスタンス化することができました
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()
if let testObject = instance as? TestObject {
println("yes!")
}
(Obj-Cを使用せずに)AnyClass
から作成する方法が見つかりませんでしたString
。基本的に型システムを壊してしまうので、そうしてほしくないのではないかと思います。
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)
}
これにより、インスタンス化するクラスの名前が取得されます。次に、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
型を表します。
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)
}
私はこのカテゴリを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)。うまくいけば、これは将来のバージョンで変更されるものです。
を使用NSClassFromString
して型の変数を取得できますがAnyClass
、Swiftにはそれをインスタンス化する方法がないようです。Objective Cへのブリッジを使用してそこで実行するか、または-それが機能する場合は-switchステートメントの使用にフォールバックできます。
どうやら、クラスの名前が実行時にしかわからない場合、Swiftでオブジェクトをインスタンス化することは(もう)不可能です。NSObjectのサブクラスにはObjective-Cラッパーが可能です。
少なくとも、Objective-Cラッパーなしで実行時に指定された別のオブジェクトと同じクラスのオブジェクトをインスタンス化できます(xCodeバージョン6.2-6C107aを使用)。
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
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
メソッドを実装していないため、このプロトコルが必要です。
正しい呪文は...
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の初期化を別のクロージャにバストする必要があります。これは正常にコンパイルされます。
異なるターゲットを使用していますが、この場合、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);
}
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 = ""
}
私のユースケースで動作します
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
ここに示されているページジャンプの例、希望があなたを助けることができます!
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)
}
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)
}
}
これが良い例です:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);