Самоанализ и обобщения в классе Swift

121

Я пытаюсь динамически создать classтип на основе экземпляра с использованием универсальных шаблонов, однако у меня возникают трудности с самоанализом класса.

Вот вопросы:

  • Есть ли Swift-эквивалент Obj-C self.class?
  • Есть ли способ создать экземпляр класса, используя AnyClassрезультат NSClassFromString?
  • Есть ли способ получить AnyClassили иным образом ввести информацию строго из общего параметра T? (Аналогично typeof(T)синтаксису C # )
Erik
источник
2
stackoverflow.com/a/24069875/292145 дает несколько подсказок по API отражения Swift.
Клаас
5
Objective-C self.classпревратится self.dynamicType.selfв Swift I, верю,
Филип Херманс,
1
В методе экземпляра можно вызвать метод класса следующим образом:self.dynamicType.foo()

Ответы:

109

Ну, во- первых , Swift эквивалент [NSString class]is .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выдает ошибку на игровой площадке.

Jiaaro
источник
1
Спасибо за ссылку на документы Metatype! Я полностью упустил из виду этот аспект типов, не так ли!
Erik
14
Вы можете использовать .Typeили .Protocolв объявлении переменной, напримерlet myObject: MyObject.Type = MyObject.self
Sulthan
1
Султан: Итак, MyObject.Type - это объявление, а MyObject.self - это фабричный метод (может быть вызван), а myObject - это переменная, содержащая ссылку на фабричный метод. Вызов myObject () приведет к созданию экземпляра класса MyObject. Было бы лучше, если бы имя переменной myObject было myObjectFactory?
бутчк
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 этих классов; это очень важно, потому что иначе мы не знаем измененную строку, обозначающую каждый класс.

Теперь мы можем использовать 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()во второй
Кадзи
13

Если я правильно читаю документацию, если вы имеете дело с экземплярами и, например, хотите вернуть новый экземпляр того же типа, что и объект, который вам был предоставлен, и тип может быть создан с помощью init (), вы можете сделать:

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

Я быстро протестировал это с помощью String:

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

который работал нормально.

monkeydom
источник
1
Да, там dynamicTypeработает, как я ожидал. Однако мне не удалось сравнить типы. По-настоящему широко используются дженерики, так что у меня может быть что-то вроде Generic<T>и внутри if T is Double {...}. Кажется, к сожалению, это невозможно.
Erik
1
@SiLo Вы когда-нибудь находили способ вообще спросить, относятся ли два объекта к одному классу?
matt
1
@matt Не элегантно, нет. Однако мне удалось создать Defaultableпротокол, который работает аналогично defaultключевому слову C # , и соответствующие расширения для таких типов, как Stringи Int. Добавив общее ограничение T:Defaultable, я мог проверить, передан ли аргумент is T.default().
Эрик
1
@SiLo Clever; Я бы хотел увидеть этот код! Я так понимаю, что это позволяет обойти странные ограничения на использование слова «is». Я сообщил об этих ограничениях, а также об общем отсутствии самоанализа класса. В итоге я сравнил строки с помощью NSStringFromClass, но, конечно, это работает только для потомков NSObject.
Мэтт
1
@matt К сожалению, это звучит умнее, чем есть на самом деле, потому что вам все равно нужно делать value is String.default()... и т.д., что вы бы просто сделали value is Stringвместо этого.
Эрик
13

В быстром 3

object.dynamicType

устарела.

Вместо этого используйте:

type(of:object)
J.beenie
источник
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

ПРИМЕЧАНИЕ: обратите внимание, что он также работает с протоколами, которые объект может или не может расширять.

eonist
источник
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
possen
источник
2
Этот метод не работает правильно, если переменная является типом суперкласса. Например, var x: NamedItem.Typeесли я назначу его x = Folder.Type, то x()вернет новый NamedItem, а не файл Folder. Это делает метод бесполезным для многих приложений. Считаю это ошибкой .
phatmann
1
На самом деле вы можете делать то, что я думаю, используя эту технику stackoverflow.com/questions/26290469/…
possen 02