การวิปัสสนาและยาสามัญอย่างรวดเร็ว


121

ฉันพยายามสร้างclassประเภทตามอินสแตนซ์แบบไดนามิกโดยใช้ generics แต่ฉันกำลังประสบกับความยากลำบากในการวิปัสสนาในชั้นเรียน

คำถามมีดังนี้

  • มี Swift เทียบเท่ากับ Obj-C self.classหรือไม่?
  • มีวิธีสร้างอินสแตนซ์คลาสโดยใช้AnyClassผลลัพธ์จากNSClassFromStringหรือไม่
  • มีวิธีรับAnyClassหรือพิมพ์ข้อมูลจากพารามิเตอร์ทั่วไปอย่างเคร่งครัดTหรือไม่? (คล้ายกับtypeof(T)ไวยากรณ์ของ C # )

2
stackoverflow.com/a/24069875/292145ให้คำแนะนำบางอย่างเกี่ยวกับ Swift Reflection API
Klaas

5
Objective-C self.classจะกลายเป็นself.dynamicType.selfความเชื่อของ Swift I
Filip Hermans

1
ในวิธีการอินสแตนซ์นี่คือวิธีการเรียกเมธอดคลาส:self.dynamicType.foo()

คำตอบ:


109

อย่างหนึ่งสิ่งที่เทียบเท่ากับ Swift [NSString class]คือ.self(ดูเอกสาร Metatypeแม้ว่าจะค่อนข้างบาง)

ในความเป็นจริง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! ฉันมองข้ามแง่มุมของประเภทนั้นไปโดยสิ้นเชิง!
Erik

14
คุณสามารถใช้.Typeหรือ.Protocolในการประกาศตัวแปรเช่นlet myObject: MyObject.Type = MyObject.self
Sulthan

1
Sulthan: ดังนั้น MyObject.Type คือการประกาศ แต่ MyObject.self เป็นวิธีการโรงงาน (สามารถเรียกได้) และ myObject เป็นตัวแปรที่มีการอ้างอิงถึงวิธีการโรงงาน การเรียก myObject () จะสร้างอินสแตนซ์ของคลาส MyObject จะเป็นตัวอย่างที่ดีกว่าถ้าชื่อของตัวแปร myObject คือ myObjectFactory?
bootchk

2
@ก่อนที่requiredจะลบ
fujianjin6471

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 munged ของคลาสเหล่านี้ นั่นสำคัญมากเพราะไม่อย่างนั้นเราจะไม่รู้ว่าสตริงสีเขียวที่กำหนดแต่ละคลาส

ตอนนี้เราสามารถใช้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แอตทริบิวต์ แต่ไม่ใช่ว่าคุณสามารถให้คำแนะนำเกี่ยวกับชื่อชั้นเรียนได้
Erik

1
โซลูชันที่ยอดเยี่ยมที่ยังคงใช้งานได้มากหรือน้อยตามที่เขียนไว้เมื่อ 6 ปีที่แล้ว มีเพียงสองสามคนที่ปรับแต่งสนามเด็กเล่นที่ขอ: as! NSObject.TypeในบรรทัดแรกและaClass.init()ที่สอง
Kaji

13

หากฉันอ่านเอกสารถูกต้องหากคุณจัดการกับอินสแตนซ์และเช่นต้องการส่งคืนอินสแตนซ์ใหม่ที่เป็น Type เดียวกันกับวัตถุที่คุณได้รับและ Type สามารถสร้างด้วย init () คุณสามารถทำได้:

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

ฉันทดสอบอย่างรวดเร็วด้วย String:

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

ซึ่งทำงานได้ดี


1
ใช่dynamicTypeทำงานตามที่ฉันคาดไว้ที่นั่น อย่างไรก็ตามฉันไม่สามารถเปรียบเทียบประเภทได้ การใช้งานขนาดใหญ่ที่แท้จริงคือการใช้ยาทั่วไปดังนั้นฉันจึงสามารถมีบางอย่างที่เหมือนกันGeneric<T>และอยู่ข้างในif T is Double {...}ได้ ดูเหมือนว่าจะเป็นไปไม่ได้อย่างน่าเสียดาย
Erik

1
@SiLo คุณเคยหาวิธีถามโดยทั่วไปว่าวัตถุสองชิ้นเป็นคลาสเดียวกันหรือไม่?
แมตต์

1
@matt ไม่สวยหรูไม่ฉันไม่ได้ แต่ผมก็สามารถที่จะสร้างDefaultableโปรโตคอลที่ทำงานคล้ายกับ C # 's defaultคำหลักและส่วนขยายที่เหมาะสมสำหรับประเภทชอบและString Intด้วยการเพิ่มข้อ จำกัด ทั่วไปของT:Defaultableฉันสามารถตรวจสอบว่าอาร์กิวเมนต์ผ่านis T.default()หรือไม่
Erik

1
@SiLo เคลฟเวอร์; ฉันต้องการดูรหัสนั้น! ฉันเข้าใจว่านี่เป็นการหลีกเลี่ยงข้อ จำกัด แปลก ๆ ในการใช้ "is" ฉันได้ยื่นข้อบกพร่องเกี่ยวกับข้อ จำกัด เหล่านั้นและยังขาดการวิปัสสนาในชั้นเรียนโดยทั่วไป ฉันลงเอยด้วยการเปรียบเทียบสตริงโดยใช้ NSStringFromClass แต่แน่นอนว่าใช้ได้กับผู้สืบทอด NSObject เท่านั้น
แมตต์

1
@matt น่าเสียดายที่มันฟังดูฉลาดกว่าที่เป็นจริงเพราะคุณยังต้องทำvalue is String.default()... ฯลฯ ซึ่งคุณจะทำvalue is Stringแทน
Erik


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 + เพื่อให้คลาสของคุณถูกแทรกโดยอัตโนมัติในแผนที่คลาสโดยฟังก์ชันโกลบอล "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
เทคนิคนี้ทำงานไม่ถูกต้องหากตัวแปรเป็น Type ของ superclass ตัวอย่างเช่นกำหนดvar x: NamedItem.Typeถ้าผมกำหนดx = Folder.Typeแล้วx()ส่งกลับใหม่ไม่ได้NamedItem Folderสิ่งนี้ทำให้เทคนิคนี้ไม่มีประโยชน์สำหรับหลาย ๆ แอปพลิเคชัน ผมคิดว่านี่เป็นข้อผิดพลาด
พัทมันน์

1
ที่จริงคุณสามารถทำสิ่งที่ฉันคิดว่าคุณต้องการโดยใช้เทคนิคนี้stackoverflow.com/questions/26290469/…
พอสได้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.