Swiftクラスのイントロスペクションとジェネリック


121

classジェネリックスを使用してインスタンスベースの型を動的に作成しようとしていますが、クラスのイントロスペクションで問題が発生しています。

ここに質問があります:

  • Obj-Cに相当するSwiftはありself.classますか?
  • AnyClassからの結果を使用してクラスをインスタンス化する方法はありNSClassFromStringますか?
  • AnyClassジェネリックパラメーターから厳密に情報を取得または入力する方法はありますTか?(C#のtypeof(T)構文と同様)

2
stackoverflow.com/a/24069875/292145は、SwiftリフレクションAPIに関するヒントを提供します。
Klaas 14

5
Objective-C はSwiftにself.classなるとself.dynamicType.self思います
フィリップハーマンズ2014

1
インスタンスメソッドでは、ここでは、クラスのメソッドを呼び出す方法は次のとおりです。self.dynamicType.foo()

回答:


109

1 つには、Swiftに相当するものはそう[NSString class]です.selfメタタイプのドキュメントを参照してください。ただし、かなり薄いです)。

実際にNSString.classは、動作しません!あなたが使用する必要がありますNSString.self

let s = NSString.self
var str = s()
str = "asdf"

同様に、迅速なクラスで試してみました...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

うーん…エラーは言う:

プレイグラウンドの実行に失敗しました:エラー::16:1:エラー:メタタイプ値を持つクラスタイプ 'X'のオブジェクトの構築には '@required'イニシャライザが必要です

 Y().me()
 ^
 <REPL>:3:7: note: selected implicit initializer with type '()'
 class X {
       ^

これが何を意味するのかを理解するのにしばらく時間がかかりました…クラスに @required init()

class X {
    func me() {
        println("asdf")
    }

    required init () {

    }
}

let Y = X.self

// prints "asdf"
Y().me()

一部のドキュメントではこれを.Typeと呼んでいMyClass.Typeますが、遊び場でエラーが発生します。


1
Metatype docsへのリンクをありがとう!私はタイプのその側面を完全に見落としました、ド!
Erik

14
.Typeまたは.Protocol、変数宣言で使用できます。例:let myObject: MyObject.Type = MyObject.self
Sulthan

1
スルタン:MyObject.Typeは宣言ですが、MyObject.selfはファクトリメソッド(呼び出し可能)であり、myObjectはファクトリメソッドへの参照を含む変数です。myObject()を呼び出すと、クラスMyObjectのインスタンスが生成されます。myObject変数の名前がmyObjectFactoryである場合、より良い例でしょうか?
bootchk 2015

2
@以前requiredは削除する必要があります
fujianjin6471 2015

49

使用方法は次のとおりNSClassFromStringです。最終的に何をしようとしているのスーパークラスを知っている必要があります。以下に、自分自身を記述する方法を知っているスーパークラスとサブクラスのペアを示しますprintln

@objc(Zilk) class Zilk : NSObject {
    override var description : String {return "I am a Zilk"}
}

@objc(Zork) class Zork : Zilk {
    override var description : String {return "I am a Zork"}
}

特別な@obj構文を使用して、これらのクラスのObjective-Cで変更された名前を指示していることに注意してください。それ以外の場合、各クラスを指定する変更された文字列がわからないため、これは重要です。

これでNSClassFromString、ZorkクラスまたはZilkクラスを作成するために使用できます。これは、NSObjectとして入力でき、後でクラッシュしないことがわかっているためです。

let aClass = NSClassFromString("Zork") as NSObject.Type
let anObject = aClass()
println(anObject) // "I am a Zork"

そしてそれは可逆的です。println(NSStringFromClass(anObject.dynamicType))も動作します。


最新バージョン:

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }

10
@objc(ClassName)ビットに賛成票を投じます。@objc属性については知っていましたが、クラス名についてのヒントを与えることもできませんでした。
エリック

1
6年経ってもまだ機能する優れたソリューション。ほんの2、3のマイナーが要求された遊び場を微調整します。as! NSObject.Type最初の行とaClass.init()2番目の行
Kaji

13

ドキュメントを正しく読んでいる場合、インスタンスを処理していて、たとえば、指定したオブジェクトと同じTypeの新しいインスタンスを返したい場合は、Typeをinit()で構築できます。

let typeOfObject = aGivenObject.dynamicType
var freshInstance = typeOfObject()

私はすぐにそれを文字列でテストしました:

let someType = "Fooo".dynamicType
let emptyString = someType()
let threeString = someType("Three")

うまくいきました。


1
はい、dynamicType期待どおりに機能します。ただし、タイプを比較することはできませんでした。本当の大きな用途はジェネリックスであるので、のようなものGeneric<T>を内部に持つことができますif T is Double {...}。残念ながら出来ないようです。
エリック

1
@SiLo一般的に、2つのオブジェクトが同じクラスかどうかを確認する方法を見つけましたか?
マット

1
@mattエレガントではありませんが、違います。しかし、私が作成することができたDefaultableC#のに似ていますプロトコルdefaultのようなタイプのためのキーワード、および適切な拡張をStringしてInt。の一般的な制約を追加することで、T:Defaultable引数が渡されたかどうかを確認できますis T.default()
エリック14年

1
@SiLo賢い; そのコードを見たいのですが!これは「is」の使用に関する奇妙な制限を回避していると思います。私はこれらの制限、およびクラスのイントロスペクションの一般的な欠如についてバグを報告しました。NSStringFromClassを使用して文字列を比較することになりましたが、もちろん、これはNSObjectの子孫に対してのみ機能します。
マット

1
@matt残念ながら、それは実際に行うよりも賢く聞こえます。なぜなら、まだやらなければならないからですvalue is String.default()...など、value is String代わりにやるだけです。
エリック

13

迅速な3

object.dynamicType

廃止予定です。

代わりに以下を使用してください:

type(of:object)

7

タイプ比較の迅速な実装

protocol Decoratable{}
class A:Decoratable{}
class B:Decoratable{}
let object:AnyObject = A()
object.dynamicType is A.Type//true
object.dynamicType is B.Type//false
object.dynamicType is Decoratable.Type//true

注:オブジェクトが拡張する場合としない場合があるプロトコルでも機能することに注意してください。


1

やっと動作するようになりました。少し面倒ですが、NSClassFromString()ルートでもうまくいきませんでした...

import Foundation

var classMap = Dictionary<String, AnyObject>()

func mapClass(name: String, constructor: AnyObject) -> ()
{
    classMap[name] = constructor;
}

class Factory
{
    class func create(className: String) -> AnyObject?
    {
        var something : AnyObject?

        var template : FactoryObject? = classMap[className] as? FactoryObject

        if (template)
        {
            let somethingElse : FactoryObject = template!.dynamicType()

            return somethingElse
        }

        return nil
    }
}


 import ObjectiveC

 class FactoryObject : NSObject
{
    @required init() {}
//...
}

class Foo : FactoryObject
{
    class override func initialize()
    {
        mapClass("LocalData", LocalData())
    }
    init () { super.init() }
}

var makeFoo : AnyObject? = Factory.create("Foo")

ビンゴ、「makeFoo」にはFooインスタンスが含まれています。

欠点は、クラスがFactoryObjectから派生している必要があり、Obj-C + initializeメソッドがクラスになければならないため、クラスはグローバル関数「mapClass」によってクラスマップに自動的に挿入されます。


1

Swiftの最初のリリース用に更新された、受け入れられた回答に類似したクラス階層の実装を示す別の例を次に示します。

class NamedItem : NSObject {
    func display() {
        println("display")
    }

    required override init() {
        super.init()
        println("base")
    }
}

class File : NamedItem {
    required init() {
        super.init()
        println("folder")
    }
}

class Folder : NamedItem {
    required init() {
        super.init()
        println("file")
    }
}

let y = Folder.self
y().display()
let z = File.self
z().display()

この結果を出力します:

base
file
display
base
folder
display

2
変数がスーパークラスのタイプである場合、この手法は正しく機能しません。たとえば、を指定var x: NamedItem.Typeした場合、を割り当てるとx = Folder.Type、ではなく、をx()返します。これにより、多くのアプリケーションでこのテクニックが役に立たなくなります。これはバグだと思います。NamedItemFolder
ファットマン2014年

1
実は私はあなたがこの技術を使用したいと思うものを行うことができますstackoverflow.com/questions/26290469/...を
possen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.